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