xref: /aosp_15_r20/external/bazelbuild-rules_rust/rust/private/rust.bzl (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1# Copyright 2015 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 rule implementations"""
16
17load("@bazel_skylib//lib:paths.bzl", "paths")
18load("//rust/private:common.bzl", "COMMON_PROVIDERS", "rust_common")
19load("//rust/private:providers.bzl", "BuildInfo")
20load("//rust/private:rustc.bzl", "rustc_compile_action")
21load(
22    "//rust/private:utils.bzl",
23    "can_build_metadata",
24    "compute_crate_name",
25    "crate_root_src",
26    "dedent",
27    "determine_lib_name",
28    "determine_output_hash",
29    "expand_dict_value_locations",
30    "find_toolchain",
31    "generate_output_diagnostics",
32    "get_edition",
33    "get_import_macro_deps",
34    "transform_deps",
35    "transform_sources",
36)
37
38# TODO(marco): Separate each rule into its own file.
39
40def _assert_no_deprecated_attributes(_ctx):
41    """Forces a failure if any deprecated attributes were specified
42
43    Args:
44        _ctx (ctx): The current rule's context object
45    """
46    pass
47
48def _assert_correct_dep_mapping(ctx):
49    """Forces a failure if proc_macro_deps and deps are mixed inappropriately
50
51    Args:
52        ctx (ctx): The current rule's context object
53    """
54    for dep in ctx.attr.deps:
55        if rust_common.crate_info in dep:
56            if dep[rust_common.crate_info].type == "proc-macro":
57                fail(
58                    "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
59                        ctx.label,
60                        dep.label,
61                    ),
62                )
63    for dep in ctx.attr.proc_macro_deps:
64        type = dep[rust_common.crate_info].type
65        if type != "proc-macro":
66            fail(
67                "{} listed {} in its proc_macro_deps, but it is not proc-macro, it is a {}. It should probably instead be listed in deps.".format(
68                    ctx.label,
69                    dep.label,
70                    type,
71                ),
72            )
73
74def _rust_library_impl(ctx):
75    """The implementation of the `rust_library` rule.
76
77    This rule provides CcInfo, so it can be used everywhere Bazel
78    expects rules_cc, but care must be taken to have the correct
79    dependencies on an allocator and std implemetation as needed.
80
81    Args:
82        ctx (ctx): The rule's context object
83
84    Returns:
85        list: A list of providers.
86    """
87    return _rust_library_common(ctx, "rlib")
88
89def _rust_static_library_impl(ctx):
90    """The implementation of the `rust_static_library` rule.
91
92    This rule provides CcInfo, so it can be used everywhere Bazel
93    expects rules_cc.
94
95    Args:
96        ctx (ctx): The rule's context object
97
98    Returns:
99        list: A list of providers.
100    """
101    return _rust_library_common(ctx, "staticlib")
102
103def _rust_shared_library_impl(ctx):
104    """The implementation of the `rust_shared_library` rule.
105
106    This rule provides CcInfo, so it can be used everywhere Bazel
107    expects rules_cc.
108
109    On Windows, a PDB file containing debugging information is available under
110    the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
111    is available under the key `dsym_folder` in `OutputGroupInfo`.
112
113    Args:
114        ctx (ctx): The rule's context object
115
116    Returns:
117        list: A list of providers.
118    """
119    return _rust_library_common(ctx, "cdylib")
120
121def _rust_proc_macro_impl(ctx):
122    """The implementation of the `rust_proc_macro` rule.
123
124    Args:
125        ctx (ctx): The rule's context object
126
127    Returns:
128        list: A list of providers.
129    """
130    return _rust_library_common(ctx, "proc-macro")
131
132def _rust_library_common(ctx, crate_type):
133    """The common implementation of the library-like rules.
134
135    Args:
136        ctx (ctx): The rule's context object
137        crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro
138
139    Returns:
140        list: A list of providers. See `rustc_compile_action`
141    """
142    _assert_no_deprecated_attributes(ctx)
143    _assert_correct_dep_mapping(ctx)
144
145    toolchain = find_toolchain(ctx)
146
147    crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
148
149    crate_root = getattr(ctx.file, "crate_root", None)
150    if not crate_root:
151        crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_type)
152    srcs, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root)
153
154    # Determine unique hash for this rlib.
155    # Note that we don't include a hash for `cdylib` and `staticlib` since they are meant to be consumed externally
156    # and having a deterministic name is important since it ends up embedded in the executable. This is problematic
157    # when one needs to include the library with a specific filename into a larger application.
158    # (see https://github.com/bazelbuild/rules_rust/issues/405#issuecomment-993089889 for more details)
159    if crate_type in ["cdylib", "staticlib"]:
160        output_hash = None
161    else:
162        output_hash = determine_output_hash(crate_root, ctx.label)
163
164    rust_lib_name = determine_lib_name(
165        crate_name,
166        crate_type,
167        toolchain,
168        output_hash,
169    )
170    rust_lib = ctx.actions.declare_file(rust_lib_name)
171    rust_metadata = None
172    rustc_rmeta_output = None
173    if can_build_metadata(toolchain, ctx, crate_type) and not ctx.attr.disable_pipelining:
174        rust_metadata = ctx.actions.declare_file(
175            paths.replace_extension(rust_lib_name, ".rmeta"),
176            sibling = rust_lib,
177        )
178        rustc_rmeta_output = generate_output_diagnostics(ctx, rust_metadata)
179
180    deps = transform_deps(ctx.attr.deps)
181    proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
182
183    return rustc_compile_action(
184        ctx = ctx,
185        attr = ctx.attr,
186        toolchain = toolchain,
187        output_hash = output_hash,
188        crate_info_dict = dict(
189            name = crate_name,
190            type = crate_type,
191            root = crate_root,
192            srcs = depset(srcs),
193            deps = depset(deps),
194            proc_macro_deps = depset(proc_macro_deps),
195            aliases = ctx.attr.aliases,
196            output = rust_lib,
197            rustc_output = generate_output_diagnostics(ctx, rust_lib),
198            metadata = rust_metadata,
199            rustc_rmeta_output = rustc_rmeta_output,
200            edition = get_edition(ctx.attr, toolchain, ctx.label),
201            rustc_env = ctx.attr.rustc_env,
202            rustc_env_files = ctx.files.rustc_env_files,
203            is_test = False,
204            data = depset(ctx.files.data),
205            compile_data = depset(ctx.files.compile_data),
206            compile_data_targets = depset(ctx.attr.compile_data),
207            owner = ctx.label,
208        ),
209    )
210
211def _rust_binary_impl(ctx):
212    """The implementation of the `rust_binary` rule
213
214    Args:
215        ctx (ctx): The rule's context object
216
217    Returns:
218        list: A list of providers. See `rustc_compile_action`
219    """
220    toolchain = find_toolchain(ctx)
221    crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
222    _assert_correct_dep_mapping(ctx)
223
224    output = ctx.actions.declare_file(ctx.label.name + toolchain.binary_ext)
225
226    deps = transform_deps(ctx.attr.deps)
227    proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
228
229    crate_root = getattr(ctx.file, "crate_root", None)
230    if not crate_root:
231        crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, ctx.attr.crate_type)
232    srcs, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root)
233
234    providers = rustc_compile_action(
235        ctx = ctx,
236        attr = ctx.attr,
237        toolchain = toolchain,
238        crate_info_dict = dict(
239            name = crate_name,
240            type = ctx.attr.crate_type,
241            root = crate_root,
242            srcs = depset(srcs),
243            deps = depset(deps),
244            proc_macro_deps = depset(proc_macro_deps),
245            aliases = ctx.attr.aliases,
246            output = output,
247            rustc_output = generate_output_diagnostics(ctx, output),
248            edition = get_edition(ctx.attr, toolchain, ctx.label),
249            rustc_env = ctx.attr.rustc_env,
250            rustc_env_files = ctx.files.rustc_env_files,
251            is_test = False,
252            data = depset(ctx.files.data),
253            compile_data = depset(ctx.files.compile_data),
254            compile_data_targets = depset(ctx.attr.compile_data),
255            owner = ctx.label,
256        ),
257    )
258
259    providers.append(RunEnvironmentInfo(
260        environment = expand_dict_value_locations(
261            ctx,
262            ctx.attr.env,
263            ctx.attr.data,
264        ),
265    ))
266
267    return providers
268
269def get_rust_test_flags(attr):
270    """Determine the desired rustc flags for test targets.
271
272    Args:
273        attr (dict): Attributes of a rule
274
275    Returns:
276        List: A list of test flags
277    """
278    if getattr(attr, "use_libtest_harness", True):
279        rust_flags = ["--test"]
280    else:
281        rust_flags = ["--cfg", "test"]
282
283    return rust_flags
284
285def _rust_test_impl(ctx):
286    """The implementation of the `rust_test` rule.
287
288    Args:
289        ctx (ctx): The ctx object for the current target.
290
291    Returns:
292        list: The list of providers. See `rustc_compile_action`
293    """
294    _assert_no_deprecated_attributes(ctx)
295    _assert_correct_dep_mapping(ctx)
296
297    toolchain = find_toolchain(ctx)
298
299    crate_type = "bin"
300    deps = transform_deps(ctx.attr.deps)
301    proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
302
303    if ctx.attr.crate and ctx.attr.srcs:
304        fail("rust_test.crate and rust_test.srcs are mutually exclusive. Update {} to use only one of these attributes".format(
305            ctx.label,
306        ))
307
308    if ctx.attr.crate:
309        # Target is building the crate in `test` config
310        crate = ctx.attr.crate[rust_common.crate_info] if rust_common.crate_info in ctx.attr.crate else ctx.attr.crate[rust_common.test_crate_info].crate
311
312        output_hash = determine_output_hash(crate.root, ctx.label)
313        output = ctx.actions.declare_file(
314            "test-%s/%s%s" % (
315                output_hash,
316                ctx.label.name,
317                toolchain.binary_ext,
318            ),
319        )
320
321        srcs, crate_root = transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
322
323        # Optionally join compile data
324        if crate.compile_data:
325            compile_data = depset(ctx.files.compile_data, transitive = [crate.compile_data])
326        else:
327            compile_data = depset(ctx.files.compile_data)
328        if crate.compile_data_targets:
329            compile_data_targets = depset(ctx.attr.compile_data, transitive = [crate.compile_data_targets])
330        else:
331            compile_data_targets = depset(ctx.attr.compile_data)
332        rustc_env_files = ctx.files.rustc_env_files + crate.rustc_env_files
333
334        # crate.rustc_env is already expanded upstream in rust_library rule implementation
335        rustc_env = dict(crate.rustc_env)
336        data_paths = depset(direct = getattr(ctx.attr, "data", [])).to_list()
337        rustc_env.update(expand_dict_value_locations(
338            ctx,
339            ctx.attr.rustc_env,
340            data_paths,
341        ))
342
343        # Build the test binary using the dependency's srcs.
344        crate_info_dict = dict(
345            name = crate.name,
346            type = crate_type,
347            root = crate.root,
348            srcs = depset(srcs, transitive = [crate.srcs]),
349            deps = depset(deps, transitive = [crate.deps]),
350            proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
351            aliases = ctx.attr.aliases,
352            output = output,
353            rustc_output = generate_output_diagnostics(ctx, output),
354            edition = crate.edition,
355            rustc_env = rustc_env,
356            rustc_env_files = rustc_env_files,
357            is_test = True,
358            compile_data = compile_data,
359            compile_data_targets = compile_data_targets,
360            wrapped_crate_type = crate.type,
361            owner = ctx.label,
362        )
363    else:
364        crate_root = getattr(ctx.file, "crate_root", None)
365
366        if not crate_root:
367            crate_root_type = "lib" if ctx.attr.use_libtest_harness else "bin"
368            crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_root_type)
369        srcs, crate_root = transform_sources(ctx, ctx.files.srcs, crate_root)
370
371        output_hash = determine_output_hash(crate_root, ctx.label)
372        output = ctx.actions.declare_file(
373            "test-%s/%s%s" % (
374                output_hash,
375                ctx.label.name,
376                toolchain.binary_ext,
377            ),
378        )
379
380        data_paths = depset(direct = getattr(ctx.attr, "data", [])).to_list()
381        rustc_env = expand_dict_value_locations(
382            ctx,
383            ctx.attr.rustc_env,
384            data_paths,
385        )
386
387        # Target is a standalone crate. Build the test binary as its own crate.
388        crate_info_dict = dict(
389            name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name),
390            type = crate_type,
391            root = crate_root,
392            srcs = depset(srcs),
393            deps = depset(deps),
394            proc_macro_deps = depset(proc_macro_deps),
395            aliases = ctx.attr.aliases,
396            output = output,
397            rustc_output = generate_output_diagnostics(ctx, output),
398            edition = get_edition(ctx.attr, toolchain, ctx.label),
399            rustc_env = rustc_env,
400            rustc_env_files = ctx.files.rustc_env_files,
401            is_test = True,
402            compile_data = depset(ctx.files.compile_data),
403            compile_data_targets = depset(ctx.attr.compile_data),
404            owner = ctx.label,
405        )
406
407    providers = rustc_compile_action(
408        ctx = ctx,
409        attr = ctx.attr,
410        toolchain = toolchain,
411        crate_info_dict = crate_info_dict,
412        rust_flags = get_rust_test_flags(ctx.attr),
413        skip_expanding_rustc_env = True,
414    )
415    data = getattr(ctx.attr, "data", [])
416
417    env = expand_dict_value_locations(
418        ctx,
419        getattr(ctx.attr, "env", {}),
420        data,
421    )
422    if toolchain.llvm_cov and ctx.configuration.coverage_enabled:
423        if not toolchain.llvm_profdata:
424            fail("toolchain.llvm_profdata is required if toolchain.llvm_cov is set.")
425
426        if toolchain._experimental_use_coverage_metadata_files:
427            llvm_cov_path = toolchain.llvm_cov.path
428            llvm_profdata_path = toolchain.llvm_profdata.path
429        else:
430            llvm_cov_path = toolchain.llvm_cov.short_path
431            if llvm_cov_path.startswith("../"):
432                llvm_cov_path = llvm_cov_path[len("../"):]
433
434            llvm_profdata_path = toolchain.llvm_profdata.short_path
435            if llvm_profdata_path.startswith("../"):
436                llvm_profdata_path = llvm_profdata_path[len("../"):]
437
438        env["RUST_LLVM_COV"] = llvm_cov_path
439        env["RUST_LLVM_PROFDATA"] = llvm_profdata_path
440    components = "{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/")
441    env["CARGO_MANIFEST_DIR"] = "/".join([c for c in components if c])
442    providers.append(testing.TestEnvironment(env))
443
444    return providers
445
446def _rust_library_group_impl(ctx):
447    dep_variant_infos = []
448    dep_variant_transitive_infos = []
449    runfiles = []
450
451    for dep in ctx.attr.deps:
452        if rust_common.crate_info in dep:
453            dep_variant_infos.append(rust_common.dep_variant_info(
454                crate_info = dep[rust_common.crate_info] if rust_common.crate_info in dep else None,
455                dep_info = dep[rust_common.dep_info] if rust_common.crate_info in dep else None,
456                build_info = dep[BuildInfo] if BuildInfo in dep else None,
457                cc_info = dep[CcInfo] if CcInfo in dep else None,
458                crate_group_info = None,
459            ))
460        elif rust_common.crate_group_info in dep:
461            dep_variant_transitive_infos.append(dep[rust_common.crate_group_info].dep_variant_infos)
462        else:
463            fail("crate_group_info targets can only depend on rust_library or rust_library_group targets.")
464
465        if dep[DefaultInfo].default_runfiles != None:
466            runfiles.append(dep[DefaultInfo].default_runfiles)
467
468    return [
469        rust_common.crate_group_info(
470            dep_variant_infos = depset(dep_variant_infos, transitive = dep_variant_transitive_infos),
471        ),
472        DefaultInfo(runfiles = ctx.runfiles().merge_all(runfiles)),
473        coverage_common.instrumented_files_info(
474            ctx,
475            dependency_attributes = ["deps"],
476        ),
477    ]
478
479def _stamp_attribute(default_value):
480    return attr.int(
481        doc = dedent("""\
482            Whether to encode build information into the `Rustc` action. Possible values:
483
484            - `stamp = 1`: Always stamp the build information into the `Rustc` action, even in \
485            [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \
486            This setting should be avoided, since it potentially kills remote caching for the target and \
487            any downstream actions that depend on it.
488
489            - `stamp = 0`: Always replace build information by constant values. This gives good build result caching.
490
491            - `stamp = -1`: Embedding of build information is controlled by the \
492            [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
493
494            Stamped targets are not rebuilt unless their dependencies change.
495
496            For example if a `rust_library` is stamped, and a `rust_binary` depends on that library, the stamped
497            library won't be rebuilt when we change sources of the `rust_binary`. This is different from how
498            [`cc_library.linkstamps`](https://docs.bazel.build/versions/main/be/c-cpp.html#cc_library.linkstamp)
499            behaves.
500        """),
501        default = default_value,
502        values = [1, 0, -1],
503    )
504
505# Internal attributes core to Rustc actions.
506RUSTC_ATTRS = {
507    "_cc_toolchain": attr.label(
508        doc = (
509            "In order to use find_cc_toolchain, your rule has to depend " +
510            "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
511            "docs for details."
512        ),
513        default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
514    ),
515    "_error_format": attr.label(
516        default = Label("//:error_format"),
517    ),
518    "_extra_exec_rustc_flag": attr.label(
519        default = Label("//:extra_exec_rustc_flag"),
520    ),
521    "_extra_exec_rustc_flags": attr.label(
522        default = Label("//:extra_exec_rustc_flags"),
523    ),
524    "_extra_rustc_flag": attr.label(
525        default = Label("//:extra_rustc_flag"),
526    ),
527    "_extra_rustc_flags": attr.label(
528        default = Label("//:extra_rustc_flags"),
529    ),
530    "_is_proc_macro_dep": attr.label(
531        default = Label("//rust/private:is_proc_macro_dep"),
532    ),
533    "_is_proc_macro_dep_enabled": attr.label(
534        default = Label("//rust/private:is_proc_macro_dep_enabled"),
535    ),
536    "_per_crate_rustc_flag": attr.label(
537        default = Label("//:experimental_per_crate_rustc_flag"),
538    ),
539    "_process_wrapper": attr.label(
540        doc = "A process wrapper for running rustc on all platforms.",
541        default = Label("//util/process_wrapper"),
542        executable = True,
543        allow_single_file = True,
544        cfg = "exec",
545    ),
546    "_rustc_output_diagnostics": attr.label(
547        default = Label("//:rustc_output_diagnostics"),
548    ),
549}
550
551_common_attrs = {
552    "aliases": attr.label_keyed_string_dict(
553        doc = dedent("""\
554            Remap crates to a new name or moniker for linkage to this target
555
556            These are other `rust_library` targets and will be presented as the new name given.
557        """),
558    ),
559    "alwayslink": attr.bool(
560        doc = dedent("""\
561            If 1, any binary that depends (directly or indirectly) on this library
562            will link in all the object files even if some contain no symbols referenced by the binary.
563
564            This attribute is used by the C++ Starlark API when passing CcInfo providers.
565        """),
566        default = False,
567    ),
568    "compile_data": attr.label_list(
569        doc = dedent("""\
570            List of files used by this rule at compile time.
571
572            This attribute can be used to specify any data files that are embedded into
573            the library, such as via the
574            [`include_str!`](https://doc.rust-lang.org/std/macro.include_str!.html)
575            macro.
576        """),
577        allow_files = True,
578    ),
579    "crate_features": attr.string_list(
580        doc = dedent("""\
581            List of features to enable for this crate.
582
583            Features are defined in the code using the `#[cfg(feature = "foo")]`
584            configuration option. The features listed here will be passed to `rustc`
585            with `--cfg feature="${feature_name}"` flags.
586        """),
587    ),
588    "crate_name": attr.string(
589        doc = dedent("""\
590            Crate name to use for this target.
591
592            This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores.
593            Defaults to the target name, with any hyphens replaced by underscores.
594        """),
595    ),
596    "crate_root": attr.label(
597        doc = dedent("""\
598            The file that will be passed to `rustc` to be used for building this crate.
599
600            If `crate_root` is not set, then this rule will look for a `lib.rs` file (or `main.rs` for rust_binary)
601            or the single file in `srcs` if `srcs` contains only one file.
602        """),
603        allow_single_file = [".rs"],
604    ),
605    "data": attr.label_list(
606        doc = dedent("""\
607            List of files used by this rule at compile time and runtime.
608
609            If including data at compile time with include_str!() and similar,
610            prefer `compile_data` over `data`, to prevent the data also being included
611            in the runfiles.
612        """),
613        allow_files = True,
614    ),
615    "deps": attr.label_list(
616        doc = dedent("""\
617            List of other libraries to be linked to this library target.
618
619            These can be either other `rust_library` targets or `cc_library` targets if
620            linking a native library.
621        """),
622    ),
623    "edition": attr.string(
624        doc = "The rust edition to use for this crate. Defaults to the edition specified in the rust_toolchain.",
625    ),
626    # Previously `proc_macro_deps` were a part of `deps`, and then proc_macro_host_transition was
627    # used into cfg="host" using `@local_config_platform//:host`.
628    # This fails for remote execution, which needs cfg="exec", and there isn't anything like
629    # `@local_config_platform//:exec` exposed.
630    "proc_macro_deps": attr.label_list(
631        doc = dedent("""\
632            List of `rust_proc_macro` targets used to help build this library target.
633        """),
634        cfg = "exec",
635        providers = [rust_common.crate_info],
636    ),
637    "rustc_env": attr.string_dict(
638        doc = dedent("""\
639            Dictionary of additional `"key": "value"` environment variables to set for rustc.
640
641            rust_test()/rust_binary() rules can use $(rootpath //package:target) to pass in the
642            location of a generated file or external tool. Cargo build scripts that wish to
643            expand locations should use cargo_build_script()'s build_script_env argument instead,
644            as build scripts are run in a different environment - see cargo_build_script()'s
645            documentation for more.
646        """),
647    ),
648    "rustc_env_files": attr.label_list(
649        doc = dedent("""\
650            Files containing additional environment variables to set for rustc.
651
652            These files should  contain a single variable per line, of format
653            `NAME=value`, and newlines may be included in a value by ending a
654            line with a trailing back-slash (`\\\\`).
655
656            The order that these files will be processed is unspecified, so
657            multiple definitions of a particular variable are discouraged.
658
659            Note that the variables here are subject to
660            [workspace status](https://docs.bazel.build/versions/main/user-manual.html#workspace_status)
661            stamping should the `stamp` attribute be enabled. Stamp variables
662            should be wrapped in brackets in order to be resolved. E.g.
663            `NAME={WORKSPACE_STATUS_VARIABLE}`.
664        """),
665        allow_files = True,
666    ),
667    "rustc_flags": attr.string_list(
668        doc = dedent("""\
669            List of compiler flags passed to `rustc`.
670
671            These strings are subject to Make variable expansion for predefined
672            source/output path variables like `$location`, `$execpath`, and
673            `$rootpath`. This expansion is useful if you wish to pass a generated
674            file of arguments to rustc: `@$(location //package:target)`.
675        """),
676    ),
677    # TODO(stardoc): How do we provide additional documentation to an inherited attribute?
678    # "name": attr.string(
679    #     doc = "This name will also be used as the name of the crate built by this rule.",
680    # `),
681    "srcs": attr.label_list(
682        doc = dedent("""\
683            List of Rust `.rs` source files used to build the library.
684
685            If `srcs` contains more than one file, then there must be a file either
686            named `lib.rs`. Otherwise, `crate_root` must be set to the source file that
687            is the root of the crate to be passed to rustc to build this crate.
688        """),
689        allow_files = [".rs"],
690        # Allow use of --compile_one_dependency with rust targets. Support for this feature for
691        # non-builtin rulesets is undocumented outside of the bazel source:
692        # https://github.com/bazelbuild/bazel/blob/7.1.1/src/main/java/com/google/devtools/build/lib/packages/Attribute.java#L102
693        flags = ["DIRECT_COMPILE_TIME_INPUT"],
694    ),
695    "stamp": _stamp_attribute(
696        default_value = 0,
697    ),
698    "version": attr.string(
699        doc = "A version to inject in the cargo environment variable.",
700        default = "0.0.0",
701    ),
702    "_stamp_flag": attr.label(
703        doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
704        default = Label("//rust/private:stamp"),
705    ),
706} | RUSTC_ATTRS
707
708_coverage_attrs = {
709    "_collect_cc_coverage": attr.label(
710        default = Label("//util/collect_coverage"),
711        executable = True,
712        cfg = "exec",
713    ),
714    # Bazel’s coverage runner
715    # (https://github.com/bazelbuild/bazel/blob/6.0.0/tools/test/collect_coverage.sh)
716    # needs a binary called “lcov_merge.”  Its location is passed in the
717    # LCOV_MERGER environmental variable.  For builtin rules, this variable
718    # is set automatically based on a magic “$lcov_merger” or
719    # “:lcov_merger” attribute, but it’s not possible to create such
720    # attributes in Starlark.  Therefore we specify the variable ourselves.
721    # Note that the coverage runner runs in the runfiles root instead of
722    # the execution root, therefore we use “path” instead of “short_path.”
723    "_lcov_merger": attr.label(
724        default = configuration_field(fragment = "coverage", name = "output_generator"),
725        executable = True,
726        cfg = "exec",
727    ),
728}
729
730_experimental_use_cc_common_link_attrs = {
731    "experimental_use_cc_common_link": attr.int(
732        doc = (
733            "Whether to use cc_common.link to link rust binaries. " +
734            "Possible values: [-1, 0, 1]. " +
735            "-1 means use the value of the toolchain.experimental_use_cc_common_link " +
736            "boolean build setting to determine. " +
737            "0 means do not use cc_common.link (use rustc instead). " +
738            "1 means use cc_common.link."
739        ),
740        values = [-1, 0, 1],
741        default = -1,
742    ),
743    "malloc": attr.label(
744        default = Label("@bazel_tools//tools/cpp:malloc"),
745        doc = """Override the default dependency on `malloc`.
746
747By default, Rust binaries linked with cc_common.link are linked against
748`@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use
749libc's `malloc`. This label must refer to a `cc_library` rule.
750""",
751        mandatory = False,
752        providers = [[CcInfo]],
753    ),  # A late-bound attribute denoting the value of the `--custom_malloc`
754    # command line flag (or None if the flag is not provided).
755    "_custom_malloc": attr.label(
756        default = configuration_field(
757            fragment = "cpp",
758            name = "custom_malloc",
759        ),
760        providers = [[CcInfo]],
761    ),
762}
763
764_rust_test_attrs = dict({
765    "crate": attr.label(
766        mandatory = False,
767        doc = dedent("""\
768            Target inline tests declared in the given crate
769
770            These tests are typically those that would be held out under
771            `#[cfg(test)]` declarations.
772        """),
773    ),
774    "env": attr.string_dict(
775        mandatory = False,
776        doc = dedent("""\
777            Specifies additional environment variables to set when the test is executed by bazel test.
778            Values are subject to `$(rootpath)`, `$(execpath)`, location, and
779            ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution.
780        """),
781    ),
782    "use_libtest_harness": attr.bool(
783        mandatory = False,
784        default = True,
785        doc = dedent("""\
786            Whether to use `libtest`. For targets using this flag, individual tests can be run by using the
787            [--test_arg](https://docs.bazel.build/versions/4.0.0/command-line-reference.html#flag--test_arg) flag.
788            E.g. `bazel test //src:rust_test --test_arg=foo::test::test_fn`.
789        """),
790    ),
791    "_use_grep_includes": attr.bool(default = True),
792}.items() + _coverage_attrs.items() + _experimental_use_cc_common_link_attrs.items())
793
794rust_library = rule(
795    implementation = _rust_library_impl,
796    provides = COMMON_PROVIDERS,
797    attrs = dict(_common_attrs.items() + {
798        "disable_pipelining": attr.bool(
799            default = False,
800            doc = dedent("""\
801                Disables pipelining for this rule if it is globally enabled.
802                This will cause this rule to not produce a `.rmeta` file and all the dependent
803                crates will instead use the `.rlib` file.
804            """),
805        ),
806    }.items()),
807    fragments = ["cpp"],
808    toolchains = [
809        str(Label("//rust:toolchain_type")),
810        "@bazel_tools//tools/cpp:toolchain_type",
811    ],
812    doc = dedent("""\
813        Builds a Rust library crate.
814
815        Example:
816
817        Suppose you have the following directory structure for a simple Rust library crate:
818
819        ```output
820        [workspace]/
821            WORKSPACE
822            hello_lib/
823                BUILD
824                src/
825                    greeter.rs
826                    lib.rs
827        ```
828
829        `hello_lib/src/greeter.rs`:
830        ```rust
831        pub struct Greeter {
832            greeting: String,
833        }
834
835        impl Greeter {
836            pub fn new(greeting: &str) -> Greeter {
837                Greeter { greeting: greeting.to_string(), }
838            }
839
840            pub fn greet(&self, thing: &str) {
841                println!("{} {}", &self.greeting, thing);
842            }
843        }
844        ```
845
846        `hello_lib/src/lib.rs`:
847
848        ```rust
849        pub mod greeter;
850        ```
851
852        `hello_lib/BUILD`:
853        ```python
854        package(default_visibility = ["//visibility:public"])
855
856        load("@rules_rust//rust:defs.bzl", "rust_library")
857
858        rust_library(
859            name = "hello_lib",
860            srcs = [
861                "src/greeter.rs",
862                "src/lib.rs",
863            ],
864        )
865        ```
866
867        Build the library:
868        ```output
869        $ bazel build //hello_lib
870        INFO: Found 1 target...
871        Target //examples/rust/hello_lib:hello_lib up-to-date:
872        bazel-bin/examples/rust/hello_lib/libhello_lib.rlib
873        INFO: Elapsed time: 1.245s, Critical Path: 1.01s
874        ```
875        """),
876)
877
878def _rust_static_library_transition_impl(settings, attr):
879    return {
880        "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"],
881    }
882
883_rust_static_library_transition = transition(
884    implementation = _rust_static_library_transition_impl,
885    inputs = [
886        "//command_line_option:platforms",
887    ],
888    outputs = [
889        "//command_line_option:platforms",
890    ],
891)
892
893rust_static_library = rule(
894    implementation = _rust_static_library_impl,
895    attrs = dict(_common_attrs.items() + {
896        "platform": attr.label(
897            doc = "Optional platform to transition the static library to.",
898            default = None,
899        ),
900        "_allowlist_function_transition": attr.label(
901            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
902        ),
903    }.items()),
904    fragments = ["cpp"],
905    cfg = _rust_static_library_transition,
906    toolchains = [
907        str(Label("//rust:toolchain_type")),
908        "@bazel_tools//tools/cpp:toolchain_type",
909    ],
910    provides = [CcInfo],
911    doc = dedent("""\
912        Builds a Rust static library.
913
914        This static library will contain all transitively reachable crates and native objects.
915        It is meant to be used when producing an artifact that is then consumed by some other build system
916        (for example to produce an archive that Python program links against).
917
918        This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
919
920        When building the whole binary in Bazel, use `rust_library` instead.
921        """),
922)
923
924def _rust_shared_library_transition_impl(settings, attr):
925    return {
926        "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"],
927    }
928
929_rust_shared_library_transition = transition(
930    implementation = _rust_shared_library_transition_impl,
931    inputs = [
932        "//command_line_option:platforms",
933    ],
934    outputs = [
935        "//command_line_option:platforms",
936    ],
937)
938
939rust_shared_library = rule(
940    implementation = _rust_shared_library_impl,
941    attrs = dict(_common_attrs.items() + _experimental_use_cc_common_link_attrs.items() + {
942        "platform": attr.label(
943            doc = "Optional platform to transition the shared library to.",
944            default = None,
945        ),
946        "_allowlist_function_transition": attr.label(
947            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
948        ),
949        "_use_grep_includes": attr.bool(default = True),
950    }.items()),
951    fragments = ["cpp"],
952    cfg = _rust_shared_library_transition,
953    toolchains = [
954        str(Label("//rust:toolchain_type")),
955        "@bazel_tools//tools/cpp:toolchain_type",
956    ],
957    provides = [CcInfo],
958    doc = dedent("""\
959        Builds a Rust shared library.
960
961        This shared library will contain all transitively reachable crates and native objects.
962        It is meant to be used when producing an artifact that is then consumed by some other build system
963        (for example to produce a shared library that Python program links against).
964
965        This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
966
967        When building the whole binary in Bazel, use `rust_library` instead.
968        """),
969)
970
971def _proc_macro_dep_transition_impl(settings, _attr):
972    if settings["//rust/private:is_proc_macro_dep_enabled"]:
973        return {"//rust/private:is_proc_macro_dep": True}
974    else:
975        return []
976
977_proc_macro_dep_transition = transition(
978    inputs = ["//rust/private:is_proc_macro_dep_enabled"],
979    outputs = ["//rust/private:is_proc_macro_dep"],
980    implementation = _proc_macro_dep_transition_impl,
981)
982
983rust_proc_macro = rule(
984    implementation = _rust_proc_macro_impl,
985    provides = COMMON_PROVIDERS,
986    # Start by copying the common attributes, then override the `deps` attribute
987    # to apply `_proc_macro_dep_transition`. To add this transition we additionally
988    # need to declare `_allowlist_function_transition`, see
989    # https://docs.bazel.build/versions/main/skylark/config.html#user-defined-transitions.
990    attrs = dict(
991        _common_attrs.items(),
992        _allowlist_function_transition = attr.label(
993            default = Label("//tools/allowlists/function_transition_allowlist"),
994        ),
995        deps = attr.label_list(
996            doc = dedent("""\
997                List of other libraries to be linked to this library target.
998
999                These can be either other `rust_library` targets or `cc_library` targets if
1000                linking a native library.
1001            """),
1002            cfg = _proc_macro_dep_transition,
1003        ),
1004    ),
1005    fragments = ["cpp"],
1006    toolchains = [
1007        str(Label("//rust:toolchain_type")),
1008        "@bazel_tools//tools/cpp:toolchain_type",
1009    ],
1010    doc = dedent("""\
1011        Builds a Rust proc-macro crate.
1012    """),
1013)
1014
1015_rust_binary_attrs = dict({
1016    "crate_type": attr.string(
1017        doc = dedent("""\
1018            Crate type that will be passed to `rustc` to be used for building this crate.
1019
1020            This option is a temporary workaround and should be used only when building
1021            for WebAssembly targets (//rust/platform:wasi and //rust/platform:wasm).
1022        """),
1023        default = "bin",
1024    ),
1025    "env": attr.string_dict(
1026        mandatory = False,
1027        doc = dedent("""\
1028            Specifies additional environment variables to set when the target is executed by bazel run.
1029            Values are subject to `$(rootpath)`, `$(execpath)`, location, and
1030            ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution.
1031
1032            Execpath returns absolute path, and in order to be able to construct the absolute path we
1033            need to wrap the test binary in a launcher. Using a launcher comes with complications, such as
1034            more complicated debugger attachment.
1035        """),
1036    ),
1037    "linker_script": attr.label(
1038        doc = dedent("""\
1039            Link script to forward into linker via rustc options.
1040        """),
1041        allow_single_file = True,
1042    ),
1043    "out_binary": attr.bool(
1044        doc = (
1045            "Force a target, regardless of it's `crate_type`, to always mark the " +
1046            "file as executable. This attribute is only used to support wasm targets but is " +
1047            "expected to be removed following a resolution to https://github.com/bazelbuild/rules_rust/issues/771."
1048        ),
1049        default = False,
1050    ),
1051    "stamp": _stamp_attribute(default_value = -1),
1052    "_use_grep_includes": attr.bool(default = True),
1053}.items() + _experimental_use_cc_common_link_attrs.items())
1054
1055def _rust_binary_transition_impl(settings, attr):
1056    return {
1057        "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"],
1058    }
1059
1060_rust_binary_transition = transition(
1061    implementation = _rust_binary_transition_impl,
1062    inputs = [
1063        "//command_line_option:platforms",
1064    ],
1065    outputs = [
1066        "//command_line_option:platforms",
1067    ],
1068)
1069
1070rust_binary = rule(
1071    implementation = _rust_binary_impl,
1072    provides = COMMON_PROVIDERS,
1073    attrs = dict(_common_attrs.items() + _rust_binary_attrs.items() + {
1074        "platform": attr.label(
1075            doc = "Optional platform to transition the binary to.",
1076            default = None,
1077        ),
1078        "_allowlist_function_transition": attr.label(
1079            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
1080        ),
1081    }.items()),
1082    executable = True,
1083    fragments = ["cpp"],
1084    cfg = _rust_binary_transition,
1085    toolchains = [
1086        str(Label("//rust:toolchain_type")),
1087        "@bazel_tools//tools/cpp:toolchain_type",
1088    ],
1089    doc = dedent("""\
1090        Builds a Rust binary crate.
1091
1092        Example:
1093
1094        Suppose you have the following directory structure for a Rust project with a
1095        library crate, `hello_lib`, and a binary crate, `hello_world` that uses the
1096        `hello_lib` library:
1097
1098        ```output
1099        [workspace]/
1100            WORKSPACE
1101            hello_lib/
1102                BUILD
1103                src/
1104                    lib.rs
1105            hello_world/
1106                BUILD
1107                src/
1108                    main.rs
1109        ```
1110
1111        `hello_lib/src/lib.rs`:
1112        ```rust
1113        pub struct Greeter {
1114            greeting: String,
1115        }
1116
1117        impl Greeter {
1118            pub fn new(greeting: &str) -> Greeter {
1119                Greeter { greeting: greeting.to_string(), }
1120            }
1121
1122            pub fn greet(&self, thing: &str) {
1123                println!("{} {}", &self.greeting, thing);
1124            }
1125        }
1126        ```
1127
1128        `hello_lib/BUILD`:
1129        ```python
1130        package(default_visibility = ["//visibility:public"])
1131
1132        load("@rules_rust//rust:defs.bzl", "rust_library")
1133
1134        rust_library(
1135            name = "hello_lib",
1136            srcs = ["src/lib.rs"],
1137        )
1138        ```
1139
1140        `hello_world/src/main.rs`:
1141        ```rust
1142        extern crate hello_lib;
1143
1144        fn main() {
1145            let hello = hello_lib::Greeter::new("Hello");
1146            hello.greet("world");
1147        }
1148        ```
1149
1150        `hello_world/BUILD`:
1151        ```python
1152        load("@rules_rust//rust:defs.bzl", "rust_binary")
1153
1154        rust_binary(
1155            name = "hello_world",
1156            srcs = ["src/main.rs"],
1157            deps = ["//hello_lib"],
1158        )
1159        ```
1160
1161        Build and run `hello_world`:
1162        ```
1163        $ bazel run //hello_world
1164        INFO: Found 1 target...
1165        Target //examples/rust/hello_world:hello_world up-to-date:
1166        bazel-bin/examples/rust/hello_world/hello_world
1167        INFO: Elapsed time: 1.308s, Critical Path: 1.22s
1168
1169        INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world
1170        Hello world
1171        ```
1172
1173        On Windows, a PDB file containing debugging information is available under
1174        the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
1175        is available under the key `dsym_folder` in `OutputGroupInfo`.
1176"""),
1177)
1178
1179def _common_attrs_for_binary_without_process_wrapper(attrs):
1180    new_attr = dict(attrs)
1181
1182    # use a fake process wrapper
1183    new_attr["_process_wrapper"] = attr.label(
1184        default = None,
1185        executable = True,
1186        allow_single_file = True,
1187        cfg = "exec",
1188    )
1189
1190    new_attr["_bootstrap_process_wrapper"] = attr.label(
1191        default = Label("//util/process_wrapper:bootstrap_process_wrapper"),
1192        executable = True,
1193        allow_single_file = True,
1194        cfg = "exec",
1195    )
1196
1197    # fix stamp = 0
1198    new_attr["stamp"] = attr.int(
1199        doc = dedent("""\
1200            Fix `stamp = 0` as stamping is not supported when building without process_wrapper:
1201            https://github.com/bazelbuild/rules_rust/blob/8df4517d370b0c543a01ba38b63e1d5a4104b035/rust/private/rustc.bzl#L955
1202        """),
1203        default = 0,
1204        values = [0],
1205    )
1206
1207    return new_attr
1208
1209# Provides an internal rust_{binary,library} to use that we can use to build the process
1210# wrapper, this breaks the dependency of rust_* on the process wrapper by
1211# setting it to None, which the functions in rustc detect and build accordingly.
1212rust_binary_without_process_wrapper = rule(
1213    implementation = _rust_binary_impl,
1214    provides = COMMON_PROVIDERS,
1215    attrs = _common_attrs_for_binary_without_process_wrapper(_common_attrs.items() + _rust_binary_attrs.items() + {
1216        "platform": attr.label(
1217            doc = "Optional platform to transition the binary to.",
1218            default = None,
1219        ),
1220        "_allowlist_function_transition": attr.label(
1221            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
1222        ),
1223    }.items()),
1224    executable = True,
1225    fragments = ["cpp"],
1226    cfg = _rust_binary_transition,
1227    toolchains = [
1228        str(Label("//rust:toolchain_type")),
1229        "@bazel_tools//tools/cpp:toolchain_type",
1230    ],
1231)
1232
1233rust_library_without_process_wrapper = rule(
1234    implementation = _rust_library_impl,
1235    provides = COMMON_PROVIDERS,
1236    attrs = dict(_common_attrs_for_binary_without_process_wrapper(_common_attrs).items()),
1237    fragments = ["cpp"],
1238    toolchains = [
1239        str(Label("//rust:toolchain_type")),
1240        "@bazel_tools//tools/cpp:toolchain_type",
1241    ],
1242)
1243
1244def _rust_test_transition_impl(settings, attr):
1245    return {
1246        "//command_line_option:platforms": str(attr.platform) if attr.platform else settings["//command_line_option:platforms"],
1247    }
1248
1249_rust_test_transition = transition(
1250    implementation = _rust_test_transition_impl,
1251    inputs = [
1252        "//command_line_option:platforms",
1253    ],
1254    outputs = [
1255        "//command_line_option:platforms",
1256    ],
1257)
1258
1259rust_test = rule(
1260    implementation = _rust_test_impl,
1261    provides = COMMON_PROVIDERS,
1262    attrs = dict(_common_attrs.items() + _rust_test_attrs.items() + {
1263        "platform": attr.label(
1264            doc = "Optional platform to transition the test to.",
1265            default = None,
1266        ),
1267        "_allowlist_function_transition": attr.label(
1268            default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
1269        ),
1270    }.items()),
1271    executable = True,
1272    fragments = ["cpp"],
1273    cfg = _rust_test_transition,
1274    test = True,
1275    toolchains = [
1276        str(Label("//rust:toolchain_type")),
1277        "@bazel_tools//tools/cpp:toolchain_type",
1278    ],
1279    doc = dedent("""\
1280        Builds a Rust test crate.
1281
1282        Examples:
1283
1284        Suppose you have the following directory structure for a Rust library crate \
1285        with unit test code in the library sources:
1286
1287        ```output
1288        [workspace]/
1289            WORKSPACE
1290            hello_lib/
1291                BUILD
1292                src/
1293                    lib.rs
1294        ```
1295
1296        `hello_lib/src/lib.rs`:
1297        ```rust
1298        pub struct Greeter {
1299            greeting: String,
1300        }
1301
1302        impl Greeter {
1303            pub fn new(greeting: &str) -> Greeter {
1304                Greeter { greeting: greeting.to_string(), }
1305            }
1306
1307            pub fn greet(&self, thing: &str) -> String {
1308                format!("{} {}", &self.greeting, thing)
1309            }
1310        }
1311
1312        #[cfg(test)]
1313        mod test {
1314            use super::Greeter;
1315
1316            #[test]
1317            fn test_greeting() {
1318                let hello = Greeter::new("Hi");
1319                assert_eq!("Hi Rust", hello.greet("Rust"));
1320            }
1321        }
1322        ```
1323
1324        To build and run the tests, simply add a `rust_test` rule with no `srcs`
1325        and only depends on the `hello_lib` `rust_library` target via the
1326        `crate` attribute:
1327
1328        `hello_lib/BUILD`:
1329        ```python
1330        load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
1331
1332        rust_library(
1333            name = "hello_lib",
1334            srcs = ["src/lib.rs"],
1335        )
1336
1337        rust_test(
1338            name = "hello_lib_test",
1339            crate = ":hello_lib",
1340            # You may add other deps that are specific to the test configuration
1341            deps = ["//some/dev/dep"],
1342        )
1343        ```
1344
1345        Run the test with `bazel test //hello_lib:hello_lib_test`. The crate
1346        will be built using the same crate name as the underlying ":hello_lib"
1347        crate.
1348
1349        ### Example: `test` directory
1350
1351        Integration tests that live in the [`tests` directory][int-tests], they are \
1352        essentially built as separate crates. Suppose you have the following directory \
1353        structure where `greeting.rs` is an integration test for the `hello_lib` \
1354        library crate:
1355
1356        [int-tests]: http://doc.rust-lang.org/book/testing.html#the-tests-directory
1357
1358        ```output
1359        [workspace]/
1360            WORKSPACE
1361            hello_lib/
1362                BUILD
1363                src/
1364                    lib.rs
1365                tests/
1366                    greeting.rs
1367        ```
1368
1369        `hello_lib/tests/greeting.rs`:
1370        ```rust
1371        extern crate hello_lib;
1372
1373        use hello_lib;
1374
1375        #[test]
1376        fn test_greeting() {
1377            let hello = greeter::Greeter::new("Hello");
1378            assert_eq!("Hello world", hello.greeting("world"));
1379        }
1380        ```
1381
1382        To build the `greeting.rs` integration test, simply add a `rust_test` target
1383        with `greeting.rs` in `srcs` and a dependency on the `hello_lib` target:
1384
1385        `hello_lib/BUILD`:
1386        ```python
1387        load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
1388
1389        rust_library(
1390            name = "hello_lib",
1391            srcs = ["src/lib.rs"],
1392        )
1393
1394        rust_test(
1395            name = "greeting_test",
1396            srcs = ["tests/greeting.rs"],
1397            deps = [":hello_lib"],
1398        )
1399        ```
1400
1401        Run the test with `bazel test //hello_lib:greeting_test`.
1402"""),
1403)
1404
1405def rust_test_suite(name, srcs, shared_srcs = [], **kwargs):
1406    """A rule for creating a test suite for a set of `rust_test` targets.
1407
1408    This rule can be used for setting up typical rust [integration tests][it]. Given the following
1409    directory structure:
1410
1411    ```text
1412    [crate]/
1413        BUILD.bazel
1414        src/
1415            lib.rs
1416            main.rs
1417        tests/
1418            integrated_test_a.rs
1419            integrated_test_b.rs
1420            integrated_test_c.rs
1421            patterns/
1422                fibonacci_test.rs
1423            helpers/
1424                mod.rs
1425    ```
1426
1427    The rule can be used to generate [rust_test](#rust_test) targets for each source file under `tests`
1428    and a [test_suite][ts] which encapsulates all tests.
1429
1430    ```python
1431    load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test_suite")
1432
1433    rust_library(
1434        name = "math_lib",
1435        srcs = ["src/lib.rs"],
1436    )
1437
1438    rust_binary(
1439        name = "math_bin",
1440        srcs = ["src/main.rs"],
1441    )
1442
1443    rust_test_suite(
1444        name = "integrated_tests_suite",
1445        srcs = glob(["tests/**"]),
1446        shared_srcs=glob(["tests/helpers/**"]),
1447        deps = [":math_lib"],
1448    )
1449    ```
1450
1451    [it]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
1452    [ts]: https://docs.bazel.build/versions/master/be/general.html#test_suite
1453
1454    Args:
1455        name (str): The name of the `test_suite`.
1456        srcs (list): All test sources, typically `glob(["tests/**/*.rs"])`.
1457        shared_srcs (list): Optional argument for sources shared among tests, typically helper functions.
1458        **kwargs (dict): Additional keyword arguments for the underyling [rust_test](#rust_test) targets. The
1459            `tags` argument is also passed to the generated `test_suite` target.
1460    """
1461    tests = []
1462
1463    for src in srcs:
1464        if not src.endswith(".rs"):
1465            fail("srcs should have `.rs` extensions")
1466
1467        if src in shared_srcs:
1468            continue
1469
1470        # Prefixed with `name` to allow parameterization with macros
1471        # The test name should not end with `.rs`
1472        test_name = name + "_" + src[:-3]
1473        rust_test(
1474            name = test_name,
1475            crate_root = src,
1476            srcs = [src] + shared_srcs,
1477            **kwargs
1478        )
1479        tests.append(test_name)
1480
1481    native.test_suite(
1482        name = name,
1483        tests = tests,
1484        tags = kwargs.get("tags", None),
1485    )
1486
1487rust_library_group = rule(
1488    implementation = _rust_library_group_impl,
1489    provides = [rust_common.crate_group_info],
1490    attrs = {
1491        "deps": attr.label_list(
1492            doc = "Other dependencies to forward through this crate group.",
1493            providers = [[rust_common.crate_group_info], [rust_common.crate_info]],
1494        ),
1495    },
1496    doc = dedent("""\
1497        Functions as an alias for a set of dependencies.
1498
1499        Specifically, the following are equivalent:
1500
1501        ```starlark
1502        rust_library_group(
1503            name = "crate_group",
1504            deps = [
1505                ":crate1",
1506                ":crate2",
1507            ],
1508        )
1509
1510        rust_library(
1511            name = "foobar",
1512            deps = [":crate_group"],
1513            ...
1514        )
1515        ```
1516
1517        and
1518
1519        ```starlark
1520        rust_library(
1521            name = "foobar",
1522            deps = [
1523                ":crate1",
1524                ":crate2",
1525            ],
1526            ...
1527        )
1528        ```
1529    """),
1530)
1531