blob: 58cc9c4f626fb68f29aad5a9ed624e89c884ec7e [file] [log] [blame]
Brian Silvermancc09f182022-03-09 15:40:20 -08001# 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# buildifier: disable=module-docstring
16load("//rust/private:common.bzl", "rust_common")
17load("//rust/private:rustc.bzl", "rustc_compile_action")
18load(
19 "//rust/private:utils.bzl",
20 "compute_crate_name",
21 "dedent",
22 "determine_output_hash",
23 "expand_dict_value_locations",
24 "find_toolchain",
25 "get_import_macro_deps",
26 "transform_deps",
27)
28
29# TODO(marco): Separate each rule into its own file.
30
31def _assert_no_deprecated_attributes(_ctx):
32 """Forces a failure if any deprecated attributes were specified
33
34 Args:
35 _ctx (ctx): The current rule's context object
36 """
37 pass
38
39def _assert_correct_dep_mapping(ctx):
40 """Forces a failure if proc_macro_deps and deps are mixed inappropriately
41
42 Args:
43 ctx (ctx): The current rule's context object
44 """
45 for dep in ctx.attr.deps:
46 if rust_common.crate_info in dep:
47 if dep[rust_common.crate_info].type == "proc-macro":
48 fail(
49 "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
50 ctx.label,
51 dep.label,
52 ),
53 )
54 for dep in ctx.attr.proc_macro_deps:
55 type = dep[rust_common.crate_info].type
56 if type != "proc-macro":
57 fail(
58 "{} listed {} in its proc_macro_deps, but it is not proc-macro, it is a {}. It should probably instead be listed in deps.".format(
59 ctx.label,
60 dep.label,
61 type,
62 ),
63 )
64
65def _determine_lib_name(name, crate_type, toolchain, lib_hash = None):
66 """See https://github.com/bazelbuild/rules_rust/issues/405
67
68 Args:
69 name (str): The name of the current target
70 crate_type (str): The `crate_type`
71 toolchain (rust_toolchain): The current `rust_toolchain`
72 lib_hash (str, optional): The hashed crate root path
73
74 Returns:
75 str: A unique library name
76 """
77 extension = None
78 prefix = ""
79 if crate_type in ("dylib", "cdylib", "proc-macro"):
80 extension = toolchain.dylib_ext
81 elif crate_type == "staticlib":
82 extension = toolchain.staticlib_ext
83 elif crate_type in ("lib", "rlib"):
84 # All platforms produce 'rlib' here
85 extension = ".rlib"
86 prefix = "lib"
87 elif crate_type == "bin":
88 fail("crate_type of 'bin' was detected in a rust_library. Please compile " +
89 "this crate as a rust_binary instead.")
90
91 if not extension:
92 fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " +
93 "please file an issue!").format(crate_type))
94
95 prefix = "lib"
96 if (toolchain.target_triple.find("windows") != -1) and crate_type not in ("lib", "rlib"):
97 prefix = ""
98 if toolchain.target_arch == "wasm32" and crate_type == "cdylib":
99 prefix = ""
100
101 return "{prefix}{name}{lib_hash}{extension}".format(
102 prefix = prefix,
103 name = name,
104 lib_hash = "-" + lib_hash if lib_hash else "",
105 extension = extension,
106 )
107
108def get_edition(attr, toolchain):
109 """Returns the Rust edition from either the current rule's attirbutes or the current `rust_toolchain`
110
111 Args:
112 attr (struct): The current rule's attributes
113 toolchain (rust_toolchain): The `rust_toolchain` for the current target
114
115 Returns:
116 str: The target Rust edition
117 """
118 if getattr(attr, "edition"):
119 return attr.edition
120 else:
121 return toolchain.default_edition
122
123def crate_root_src(attr, srcs, crate_type):
124 """Finds the source file for the crate root.
125
126 Args:
127 attr (struct): The attributes of the current target
128 srcs (list): A list of all sources for the target Crate.
129 crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
130
131 Returns:
132 File: The root File object for a given crate. See the following links for more details:
133 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library
134 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
135 """
136 default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
137
138 crate_root = None
139 if hasattr(attr, "crate_root"):
140 if attr.crate_root:
141 crate_root = attr.crate_root.files.to_list()[0]
142
143 if not crate_root:
144 crate_root = (
145 (srcs[0] if len(srcs) == 1 else None) or
146 _shortest_src_with_basename(srcs, default_crate_root_filename) or
147 _shortest_src_with_basename(srcs, attr.name + ".rs")
148 )
149 if not crate_root:
150 file_names = [default_crate_root_filename, attr.name + ".rs"]
151 fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
152 return crate_root
153
154def _shortest_src_with_basename(srcs, basename):
155 """Finds the shortest among the paths in srcs that match the desired basename.
156
157 Args:
158 srcs (list): A list of File objects
159 basename (str): The target basename to match against.
160
161 Returns:
162 File: The File object with the shortest path that matches `basename`
163 """
164 shortest = None
165 for f in srcs:
166 if f.basename == basename:
167 if not shortest or len(f.dirname) < len(shortest.dirname):
168 shortest = f
169 return shortest
170
171def _rust_library_impl(ctx):
172 """The implementation of the `rust_library` rule.
173
174 This rule provides CcInfo, so it can be used everywhere Bazel
175 expects rules_cc, but care must be taken to have the correct
176 dependencies on an allocator and std implemetation as needed.
177
178 Args:
179 ctx (ctx): The rule's context object
180
181 Returns:
182 list: A list of providers.
183 """
184 return _rust_library_common(ctx, "rlib")
185
186def _rust_static_library_impl(ctx):
187 """The implementation of the `rust_static_library` rule.
188
189 This rule provides CcInfo, so it can be used everywhere Bazel
190 expects rules_cc.
191
192 Args:
193 ctx (ctx): The rule's context object
194
195 Returns:
196 list: A list of providers.
197 """
198 return _rust_library_common(ctx, "staticlib")
199
200def _rust_shared_library_impl(ctx):
201 """The implementation of the `rust_shared_library` rule.
202
203 This rule provides CcInfo, so it can be used everywhere Bazel
204 expects rules_cc.
205
206 On Windows, a PDB file containing debugging information is available under
207 the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
208 is available under the key `dsym_folder` in `OutputGroupInfo`.
209
210 Args:
211 ctx (ctx): The rule's context object
212
213 Returns:
214 list: A list of providers.
215 """
216 return _rust_library_common(ctx, "cdylib")
217
218def _rust_proc_macro_impl(ctx):
219 """The implementation of the `rust_proc_macro` rule.
220
221 Args:
222 ctx (ctx): The rule's context object
223
224 Returns:
225 list: A list of providers.
226 """
227 return _rust_library_common(ctx, "proc-macro")
228
229def _rust_library_common(ctx, crate_type):
230 """The common implementation of the library-like rules.
231
232 Args:
233 ctx (ctx): The rule's context object
234 crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro
235
236 Returns:
237 list: A list of providers. See `rustc_compile_action`
238 """
239
240 # Find lib.rs
241 crate_root = crate_root_src(ctx.attr, ctx.files.srcs, "lib")
242 _assert_no_deprecated_attributes(ctx)
243 _assert_correct_dep_mapping(ctx)
244
245 toolchain = find_toolchain(ctx)
246
247 # Determine unique hash for this rlib.
248 # Note that we don't include a hash for `cdylib` since they are meant to be consumed externally and having a
249 # deterministic name is important since it ends up embedded in the executable. This is problematic when one needs
250 # to include the library with a specific filename into a larger application.
251 # (see https://github.com/bazelbuild/rules_rust/issues/405#issuecomment-993089889 for more details)
252 if crate_type != "cdylib":
253 output_hash = determine_output_hash(crate_root, ctx.label)
254 else:
255 output_hash = None
256
257 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
258 rust_lib_name = _determine_lib_name(
259 crate_name,
260 crate_type,
261 toolchain,
262 output_hash,
263 )
264 rust_lib = ctx.actions.declare_file(rust_lib_name)
265
266 deps = transform_deps(ctx.attr.deps)
267 proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
268
269 return rustc_compile_action(
270 ctx = ctx,
271 attr = ctx.attr,
272 toolchain = toolchain,
273 crate_info = rust_common.create_crate_info(
274 name = crate_name,
275 type = crate_type,
276 root = crate_root,
277 srcs = depset(ctx.files.srcs),
278 deps = depset(deps),
279 proc_macro_deps = depset(proc_macro_deps),
280 aliases = ctx.attr.aliases,
281 output = rust_lib,
282 edition = get_edition(ctx.attr, toolchain),
283 rustc_env = ctx.attr.rustc_env,
284 is_test = False,
285 compile_data = depset(ctx.files.compile_data),
286 owner = ctx.label,
287 ),
288 output_hash = output_hash,
289 )
290
291def _rust_binary_impl(ctx):
292 """The implementation of the `rust_binary` rule
293
294 Args:
295 ctx (ctx): The rule's context object
296
297 Returns:
298 list: A list of providers. See `rustc_compile_action`
299 """
300 toolchain = find_toolchain(ctx)
301 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
302 _assert_correct_dep_mapping(ctx)
303
304 output = ctx.actions.declare_file(ctx.label.name + toolchain.binary_ext)
305
306 deps = transform_deps(ctx.attr.deps)
307 proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
308
309 return rustc_compile_action(
310 ctx = ctx,
311 attr = ctx.attr,
312 toolchain = toolchain,
313 crate_info = rust_common.create_crate_info(
314 name = crate_name,
315 type = ctx.attr.crate_type,
316 root = crate_root_src(ctx.attr, ctx.files.srcs, ctx.attr.crate_type),
317 srcs = depset(ctx.files.srcs),
318 deps = depset(deps),
319 proc_macro_deps = depset(proc_macro_deps),
320 aliases = ctx.attr.aliases,
321 output = output,
322 edition = get_edition(ctx.attr, toolchain),
323 rustc_env = ctx.attr.rustc_env,
324 is_test = False,
325 compile_data = depset(ctx.files.compile_data),
326 owner = ctx.label,
327 ),
328 )
329
330def _rust_test_common(ctx, toolchain, output):
331 """Builds a Rust test binary.
332
333 Args:
334 ctx (ctx): The ctx object for the current target.
335 toolchain (rust_toolchain): The current `rust_toolchain`
336 output (File): The output File that will be produced, depends on crate type.
337
338 Returns:
339 list: The list of providers. See `rustc_compile_action`
340 """
341 _assert_no_deprecated_attributes(ctx)
342 _assert_correct_dep_mapping(ctx)
343
344 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
345 crate_type = "bin"
346
347 deps = transform_deps(ctx.attr.deps)
348 proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
349
350 if ctx.attr.crate:
351 # Target is building the crate in `test` config
352 crate = ctx.attr.crate[rust_common.crate_info]
353
354 # Optionally join compile data
355 if crate.compile_data:
356 compile_data = depset(ctx.files.compile_data, transitive = [crate.compile_data])
357 else:
358 compile_data = depset(ctx.files.compile_data)
359
360 # Build the test binary using the dependency's srcs.
361 crate_info = rust_common.create_crate_info(
362 name = crate_name,
363 type = crate_type,
364 root = crate.root,
365 srcs = depset(ctx.files.srcs, transitive = [crate.srcs]),
366 deps = depset(deps, transitive = [crate.deps]),
367 proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
368 aliases = ctx.attr.aliases,
369 output = output,
370 edition = crate.edition,
371 rustc_env = ctx.attr.rustc_env,
372 is_test = True,
373 compile_data = compile_data,
374 wrapped_crate_type = crate.type,
375 owner = ctx.label,
376 )
377 else:
378 # Target is a standalone crate. Build the test binary as its own crate.
379 crate_info = rust_common.create_crate_info(
380 name = crate_name,
381 type = crate_type,
382 root = crate_root_src(ctx.attr, ctx.files.srcs, "lib"),
383 srcs = depset(ctx.files.srcs),
384 deps = depset(deps),
385 proc_macro_deps = depset(proc_macro_deps),
386 aliases = ctx.attr.aliases,
387 output = output,
388 edition = get_edition(ctx.attr, toolchain),
389 rustc_env = ctx.attr.rustc_env,
390 is_test = True,
391 compile_data = depset(ctx.files.compile_data),
392 owner = ctx.label,
393 )
394
395 providers = rustc_compile_action(
396 ctx = ctx,
397 attr = ctx.attr,
398 toolchain = toolchain,
399 crate_info = crate_info,
400 rust_flags = ["--test"] if ctx.attr.use_libtest_harness else ["--cfg", "test"],
401 )
402 data = getattr(ctx.attr, "data", [])
403
404 env = expand_dict_value_locations(
405 ctx,
406 getattr(ctx.attr, "env", {}),
407 data,
408 )
409 providers.append(testing.TestEnvironment(env))
410
411 return providers
412
413def _rust_test_impl(ctx):
414 """The implementation of the `rust_test` rule
415
416 Args:
417 ctx (ctx): The rule's context object
418
419 Returns:
420 list: A list of providers. See `_rust_test_common`
421 """
422 toolchain = find_toolchain(ctx)
423
424 output = ctx.actions.declare_file(
425 ctx.label.name + toolchain.binary_ext,
426 )
427
428 return _rust_test_common(ctx, toolchain, output)
429
430_common_attrs = {
431 "aliases": attr.label_keyed_string_dict(
432 doc = dedent("""\
433 Remap crates to a new name or moniker for linkage to this target
434
435 These are other `rust_library` targets and will be presented as the new name given.
436 """),
437 ),
438 "compile_data": attr.label_list(
439 doc = dedent("""\
440 List of files used by this rule at compile time.
441
442 This attribute can be used to specify any data files that are embedded into
443 the library, such as via the
444 [`include_str!`](https://doc.rust-lang.org/std/macro.include_str!.html)
445 macro.
446 """),
447 allow_files = True,
448 ),
449 "crate_features": attr.string_list(
450 doc = dedent("""\
451 List of features to enable for this crate.
452
453 Features are defined in the code using the `#[cfg(feature = "foo")]`
454 configuration option. The features listed here will be passed to `rustc`
455 with `--cfg feature="${feature_name}"` flags.
456 """),
457 ),
458 "crate_name": attr.string(
459 doc = dedent("""\
460 Crate name to use for this target.
461
462 This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores.
463 Defaults to the target name, with any hyphens replaced by underscores.
464 """),
465 ),
466 "crate_root": attr.label(
467 doc = dedent("""\
468 The file that will be passed to `rustc` to be used for building this crate.
469
470 If `crate_root` is not set, then this rule will look for a `lib.rs` file (or `main.rs` for rust_binary)
471 or the single file in `srcs` if `srcs` contains only one file.
472 """),
473 allow_single_file = [".rs"],
474 ),
475 "data": attr.label_list(
476 doc = dedent("""\
477 List of files used by this rule at compile time and runtime.
478
479 If including data at compile time with include_str!() and similar,
480 prefer `compile_data` over `data`, to prevent the data also being included
481 in the runfiles.
482 """),
483 allow_files = True,
484 ),
485 "deps": attr.label_list(
486 doc = dedent("""\
487 List of other libraries to be linked to this library target.
488
489 These can be either other `rust_library` targets or `cc_library` targets if
490 linking a native library.
491 """),
492 ),
493 "edition": attr.string(
494 doc = "The rust edition to use for this crate. Defaults to the edition specified in the rust_toolchain.",
495 ),
496 # Previously `proc_macro_deps` were a part of `deps`, and then proc_macro_host_transition was
497 # used into cfg="host" using `@local_config_platform//:host`.
498 # This fails for remote execution, which needs cfg="exec", and there isn't anything like
499 # `@local_config_platform//:exec` exposed.
500 "proc_macro_deps": attr.label_list(
501 doc = dedent("""\
502 List of `rust_library` targets with kind `proc-macro` used to help build this library target.
503 """),
504 cfg = "exec",
505 providers = [rust_common.crate_info],
506 ),
507 "rustc_env": attr.string_dict(
508 doc = dedent("""\
509 Dictionary of additional `"key": "value"` environment variables to set for rustc.
510
511 rust_test()/rust_binary() rules can use $(rootpath //package:target) to pass in the
512 location of a generated file or external tool. Cargo build scripts that wish to
513 expand locations should use cargo_build_script()'s build_script_env argument instead,
514 as build scripts are run in a different environment - see cargo_build_script()'s
515 documentation for more.
516 """),
517 ),
518 "rustc_env_files": attr.label_list(
519 doc = dedent("""\
520 Files containing additional environment variables to set for rustc.
521
522 These files should contain a single variable per line, of format
523 `NAME=value`, and newlines may be included in a value by ending a
524 line with a trailing back-slash (`\\\\`).
525
526 The order that these files will be processed is unspecified, so
527 multiple definitions of a particular variable are discouraged.
528
529 Note that the variables here are subject to
530 [workspace status](https://docs.bazel.build/versions/main/user-manual.html#workspace_status)
531 stamping should the `stamp` attribute be enabled. Stamp variables
532 should be wrapped in brackets in order to be resolved. E.g.
533 `NAME={WORKSPACE_STATUS_VARIABLE}`.
534 """),
535 allow_files = True,
536 ),
537 "rustc_flags": attr.string_list(
538 doc = dedent("""\
539 List of compiler flags passed to `rustc`.
540
541 These strings are subject to Make variable expansion for predefined
542 source/output path variables like `$location`, `$execpath`, and
543 `$rootpath`. This expansion is useful if you wish to pass a generated
544 file of arguments to rustc: `@$(location //package:target)`.
545 """),
546 ),
547 # TODO(stardoc): How do we provide additional documentation to an inherited attribute?
548 # "name": attr.string(
549 # doc = "This name will also be used as the name of the crate built by this rule.",
550 # `),
551 "srcs": attr.label_list(
552 doc = dedent("""\
553 List of Rust `.rs` source files used to build the library.
554
555 If `srcs` contains more than one file, then there must be a file either
556 named `lib.rs`. Otherwise, `crate_root` must be set to the source file that
557 is the root of the crate to be passed to rustc to build this crate.
558 """),
559 allow_files = [".rs"],
560 ),
561 "stamp": attr.int(
562 doc = dedent("""\
563 Whether to encode build information into the `Rustc` action. Possible values:
564
565 - `stamp = 1`: Always stamp the build information into the `Rustc` action, even in \
566 [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \
567 This setting should be avoided, since it potentially kills remote caching for the target and \
568 any downstream actions that depend on it.
569
570 - `stamp = 0`: Always replace build information by constant values. This gives good build result caching.
571
572 - `stamp = -1`: Embedding of build information is controlled by the \
573 [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
574
575 Stamped targets are not rebuilt unless their dependencies change.
576
577 For example if a `rust_library` is stamped, and a `rust_binary` depends on that library, the stamped
578 library won't be rebuilt when we change sources of the `rust_binary`. This is different from how
579 [`cc_library.linkstamps`](https://docs.bazel.build/versions/main/be/c-cpp.html#cc_library.linkstamp)
580 behaves.
581 """),
582 default = -1,
583 values = [1, 0, -1],
584 ),
585 "version": attr.string(
586 doc = "A version to inject in the cargo environment variable.",
587 default = "0.0.0",
588 ),
589 "_cc_toolchain": attr.label(
590 default = "@bazel_tools//tools/cpp:current_cc_toolchain",
591 ),
592 "_error_format": attr.label(default = "//:error_format"),
593 "_extra_exec_rustc_flags": attr.label(default = "//:extra_exec_rustc_flags"),
594 "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
595 "_import_macro_dep": attr.label(
596 default = "@rules_rust//util/import",
597 ),
598 "_process_wrapper": attr.label(
599 default = Label("//util/process_wrapper"),
600 executable = True,
601 allow_single_file = True,
602 cfg = "exec",
603 ),
604 "_stamp_flag": attr.label(
605 doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
606 default = Label("//rust/private:stamp"),
607 ),
608}
609
610_rust_test_attrs = {
611 "crate": attr.label(
612 mandatory = False,
613 doc = dedent("""\
614 Target inline tests declared in the given crate
615
616 These tests are typically those that would be held out under
617 `#[cfg(test)]` declarations.
618 """),
619 ),
620 "env": attr.string_dict(
621 mandatory = False,
622 doc = dedent("""\
623 Specifies additional environment variables to set when the test is executed by bazel test.
624 Values are subject to `$(rootpath)`, `$(execpath)`, location, and
625 ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution.
626
627 Execpath returns absolute path, and in order to be able to construct the absolute path we
628 need to wrap the test binary in a launcher. Using a launcher comes with complications, such as
629 more complicated debugger attachment.
630 """),
631 ),
632 "use_libtest_harness": attr.bool(
633 mandatory = False,
634 default = True,
635 doc = dedent("""\
636 Whether to use `libtest`. For targets using this flag, individual tests can be run by using the
637 [--test_arg](https://docs.bazel.build/versions/4.0.0/command-line-reference.html#flag--test_arg) flag.
638 E.g. `bazel test //src:rust_test --test_arg=foo::test::test_fn`.
639 """),
640 ),
641 "_grep_includes": attr.label(
642 allow_single_file = True,
643 cfg = "exec",
644 default = Label("@bazel_tools//tools/cpp:grep-includes"),
645 executable = True,
646 ),
647}
648
649_common_providers = [
650 rust_common.crate_info,
651 rust_common.dep_info,
652 DefaultInfo,
653]
654
655rust_library = rule(
656 implementation = _rust_library_impl,
657 provides = _common_providers,
658 attrs = dict(_common_attrs.items()),
659 fragments = ["cpp"],
660 host_fragments = ["cpp"],
661 toolchains = [
662 str(Label("//rust:toolchain")),
663 "@bazel_tools//tools/cpp:toolchain_type",
664 ],
665 incompatible_use_toolchain_transition = True,
666 doc = dedent("""\
667 Builds a Rust library crate.
668
669 Example:
670
671 Suppose you have the following directory structure for a simple Rust library crate:
672
673 ```output
674 [workspace]/
675 WORKSPACE
676 hello_lib/
677 BUILD
678 src/
679 greeter.rs
680 lib.rs
681 ```
682
683 `hello_lib/src/greeter.rs`:
684 ```rust
685 pub struct Greeter {
686 greeting: String,
687 }
688
689 impl Greeter {
690 pub fn new(greeting: &str) -> Greeter {
691 Greeter { greeting: greeting.to_string(), }
692 }
693
694 pub fn greet(&self, thing: &str) {
695 println!("{} {}", &self.greeting, thing);
696 }
697 }
698 ```
699
700 `hello_lib/src/lib.rs`:
701
702 ```rust
703 pub mod greeter;
704 ```
705
706 `hello_lib/BUILD`:
707 ```python
708 package(default_visibility = ["//visibility:public"])
709
710 load("@rules_rust//rust:defs.bzl", "rust_library")
711
712 rust_library(
713 name = "hello_lib",
714 srcs = [
715 "src/greeter.rs",
716 "src/lib.rs",
717 ],
718 )
719 ```
720
721 Build the library:
722 ```output
723 $ bazel build //hello_lib
724 INFO: Found 1 target...
725 Target //examples/rust/hello_lib:hello_lib up-to-date:
726 bazel-bin/examples/rust/hello_lib/libhello_lib.rlib
727 INFO: Elapsed time: 1.245s, Critical Path: 1.01s
728 ```
729 """),
730)
731
732rust_static_library = rule(
733 implementation = _rust_static_library_impl,
734 provides = _common_providers,
735 attrs = dict(_common_attrs.items()),
736 fragments = ["cpp"],
737 host_fragments = ["cpp"],
738 toolchains = [
739 str(Label("//rust:toolchain")),
740 "@bazel_tools//tools/cpp:toolchain_type",
741 ],
742 incompatible_use_toolchain_transition = True,
743 doc = dedent("""\
744 Builds a Rust static library.
745
746 This static library will contain all transitively reachable crates and native objects.
747 It is meant to be used when producing an artifact that is then consumed by some other build system
748 (for example to produce an archive that Python program links against).
749
750 This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
751
752 When building the whole binary in Bazel, use `rust_library` instead.
753 """),
754)
755
756rust_shared_library = rule(
757 implementation = _rust_shared_library_impl,
758 provides = _common_providers,
759 attrs = dict(_common_attrs.items()),
760 fragments = ["cpp"],
761 host_fragments = ["cpp"],
762 toolchains = [
763 str(Label("//rust:toolchain")),
764 "@bazel_tools//tools/cpp:toolchain_type",
765 ],
766 incompatible_use_toolchain_transition = True,
767 doc = dedent("""\
768 Builds a Rust shared library.
769
770 This shared library will contain all transitively reachable crates and native objects.
771 It is meant to be used when producing an artifact that is then consumed by some other build system
772 (for example to produce a shared library that Python program links against).
773
774 This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
775
776 When building the whole binary in Bazel, use `rust_library` instead.
777 """),
778)
779
780rust_proc_macro = rule(
781 implementation = _rust_proc_macro_impl,
782 provides = _common_providers,
783 attrs = dict(_common_attrs.items()),
784 fragments = ["cpp"],
785 host_fragments = ["cpp"],
786 toolchains = [
787 str(Label("//rust:toolchain")),
788 "@bazel_tools//tools/cpp:toolchain_type",
789 ],
790 incompatible_use_toolchain_transition = True,
791 doc = dedent("""\
792 Builds a Rust proc-macro crate.
793 """),
794)
795
796_rust_binary_attrs = {
797 "crate_type": attr.string(
798 doc = dedent("""\
799 Crate type that will be passed to `rustc` to be used for building this crate.
800
801 This option is a temporary workaround and should be used only when building
802 for WebAssembly targets (//rust/platform:wasi and //rust/platform:wasm).
803 """),
804 default = "bin",
805 ),
806 "linker_script": attr.label(
807 doc = dedent("""\
808 Link script to forward into linker via rustc options.
809 """),
810 cfg = "exec",
811 allow_single_file = True,
812 ),
813 "out_binary": attr.bool(
814 doc = (
815 "Force a target, regardless of it's `crate_type`, to always mark the " +
816 "file as executable. This attribute is only used to support wasm targets but is " +
817 "expected to be removed following a resolution to https://github.com/bazelbuild/rules_rust/issues/771."
818 ),
819 default = False,
820 ),
821 "_grep_includes": attr.label(
822 allow_single_file = True,
823 cfg = "exec",
824 default = Label("@bazel_tools//tools/cpp:grep-includes"),
825 executable = True,
826 ),
827}
828
829rust_binary = rule(
830 implementation = _rust_binary_impl,
831 provides = _common_providers,
832 attrs = dict(_common_attrs.items() + _rust_binary_attrs.items()),
833 executable = True,
834 fragments = ["cpp"],
835 host_fragments = ["cpp"],
836 toolchains = [
837 str(Label("//rust:toolchain")),
838 "@bazel_tools//tools/cpp:toolchain_type",
839 ],
840 incompatible_use_toolchain_transition = True,
841 doc = dedent("""\
842 Builds a Rust binary crate.
843
844 Example:
845
846 Suppose you have the following directory structure for a Rust project with a
847 library crate, `hello_lib`, and a binary crate, `hello_world` that uses the
848 `hello_lib` library:
849
850 ```output
851 [workspace]/
852 WORKSPACE
853 hello_lib/
854 BUILD
855 src/
856 lib.rs
857 hello_world/
858 BUILD
859 src/
860 main.rs
861 ```
862
863 `hello_lib/src/lib.rs`:
864 ```rust
865 pub struct Greeter {
866 greeting: String,
867 }
868
869 impl Greeter {
870 pub fn new(greeting: &str) -> Greeter {
871 Greeter { greeting: greeting.to_string(), }
872 }
873
874 pub fn greet(&self, thing: &str) {
875 println!("{} {}", &self.greeting, thing);
876 }
877 }
878 ```
879
880 `hello_lib/BUILD`:
881 ```python
882 package(default_visibility = ["//visibility:public"])
883
884 load("@rules_rust//rust:defs.bzl", "rust_library")
885
886 rust_library(
887 name = "hello_lib",
888 srcs = ["src/lib.rs"],
889 )
890 ```
891
892 `hello_world/src/main.rs`:
893 ```rust
894 extern crate hello_lib;
895
896 fn main() {
897 let hello = hello_lib::Greeter::new("Hello");
898 hello.greet("world");
899 }
900 ```
901
902 `hello_world/BUILD`:
903 ```python
904 load("@rules_rust//rust:defs.bzl", "rust_binary")
905
906 rust_binary(
907 name = "hello_world",
908 srcs = ["src/main.rs"],
909 deps = ["//hello_lib"],
910 )
911 ```
912
913 Build and run `hello_world`:
914 ```
915 $ bazel run //hello_world
916 INFO: Found 1 target...
917 Target //examples/rust/hello_world:hello_world up-to-date:
918 bazel-bin/examples/rust/hello_world/hello_world
919 INFO: Elapsed time: 1.308s, Critical Path: 1.22s
920
921 INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world
922 Hello world
923 ```
924
925 On Windows, a PDB file containing debugging information is available under
926 the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
927 is available under the key `dsym_folder` in `OutputGroupInfo`.
928"""),
929)
930
931rust_test = rule(
932 implementation = _rust_test_impl,
933 provides = _common_providers,
934 attrs = dict(_common_attrs.items() +
935 _rust_test_attrs.items()),
936 executable = True,
937 fragments = ["cpp"],
938 host_fragments = ["cpp"],
939 test = True,
940 toolchains = [
941 str(Label("//rust:toolchain")),
942 "@bazel_tools//tools/cpp:toolchain_type",
943 ],
944 incompatible_use_toolchain_transition = True,
945 doc = dedent("""\
946 Builds a Rust test crate.
947
948 Examples:
949
950 Suppose you have the following directory structure for a Rust library crate \
951 with unit test code in the library sources:
952
953 ```output
954 [workspace]/
955 WORKSPACE
956 hello_lib/
957 BUILD
958 src/
959 lib.rs
960 ```
961
962 `hello_lib/src/lib.rs`:
963 ```rust
964 pub struct Greeter {
965 greeting: String,
966 }
967
968 impl Greeter {
969 pub fn new(greeting: &str) -> Greeter {
970 Greeter { greeting: greeting.to_string(), }
971 }
972
973 pub fn greet(&self, thing: &str) -> String {
974 format!("{} {}", &self.greeting, thing)
975 }
976 }
977
978 #[cfg(test)]
979 mod test {
980 use super::Greeter;
981
982 #[test]
983 fn test_greeting() {
984 let hello = Greeter::new("Hi");
985 assert_eq!("Hi Rust", hello.greet("Rust"));
986 }
987 }
988 ```
989
990 To build and run the tests, simply add a `rust_test` rule with no `srcs` and \
991 only depends on the `hello_lib` `rust_library` target:
992
993 `hello_lib/BUILD`:
994 ```python
995 load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
996
997 rust_library(
998 name = "hello_lib",
999 srcs = ["src/lib.rs"],
1000 )
1001
1002 rust_test(
1003 name = "hello_lib_test",
1004 deps = [":hello_lib"],
1005 )
1006 ```
1007
1008 Run the test with `bazel build //hello_lib:hello_lib_test`.
1009
1010 To run a crate or lib with the `#[cfg(test)]` configuration, handling inline \
1011 tests, you should specify the crate directly like so.
1012
1013 ```python
1014 rust_test(
1015 name = "hello_lib_test",
1016 crate = ":hello_lib",
1017 # You may add other deps that are specific to the test configuration
1018 deps = ["//some/dev/dep"],
1019 )
1020 ```
1021
1022 ### Example: `test` directory
1023
1024 Integration tests that live in the [`tests` directory][int-tests], they are \
1025 essentially built as separate crates. Suppose you have the following directory \
1026 structure where `greeting.rs` is an integration test for the `hello_lib` \
1027 library crate:
1028
1029 [int-tests]: http://doc.rust-lang.org/book/testing.html#the-tests-directory
1030
1031 ```output
1032 [workspace]/
1033 WORKSPACE
1034 hello_lib/
1035 BUILD
1036 src/
1037 lib.rs
1038 tests/
1039 greeting.rs
1040 ```
1041
1042 `hello_lib/tests/greeting.rs`:
1043 ```rust
1044 extern crate hello_lib;
1045
1046 use hello_lib;
1047
1048 #[test]
1049 fn test_greeting() {
1050 let hello = greeter::Greeter::new("Hello");
1051 assert_eq!("Hello world", hello.greeting("world"));
1052 }
1053 ```
1054
1055 To build the `greeting.rs` integration test, simply add a `rust_test` target
1056 with `greeting.rs` in `srcs` and a dependency on the `hello_lib` target:
1057
1058 `hello_lib/BUILD`:
1059 ```python
1060 load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
1061
1062 rust_library(
1063 name = "hello_lib",
1064 srcs = ["src/lib.rs"],
1065 )
1066
1067 rust_test(
1068 name = "greeting_test",
1069 srcs = ["tests/greeting.rs"],
1070 deps = [":hello_lib"],
1071 )
1072 ```
1073
1074 Run the test with `bazel build //hello_lib:hello_lib_test`.
1075"""),
1076)
1077
1078def rust_test_suite(name, srcs, **kwargs):
1079 """A rule for creating a test suite for a set of `rust_test` targets.
1080
1081 This rule can be used for setting up typical rust [integration tests][it]. Given the following
1082 directory structure:
1083
1084 ```text
1085 [crate]/
1086 BUILD.bazel
1087 src/
1088 lib.rs
1089 main.rs
1090 tests/
1091 integrated_test_a.rs
1092 integrated_test_b.rs
1093 integrated_test_c.rs
1094 patterns/
1095 fibonacci_test.rs
1096 ```
1097
1098 The rule can be used to generate [rust_test](#rust_test) targets for each source file under `tests`
1099 and a [test_suite][ts] which encapsulates all tests.
1100
1101 ```python
1102 load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test_suite")
1103
1104 rust_library(
1105 name = "math_lib",
1106 srcs = ["src/lib.rs"],
1107 )
1108
1109 rust_binary(
1110 name = "math_bin",
1111 srcs = ["src/main.rs"],
1112 )
1113
1114 rust_test_suite(
1115 name = "integrated_tests_suite",
1116 srcs = glob(["tests/**"]),
1117 deps = [":math_lib"],
1118 )
1119 ```
1120
1121 [it]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
1122 [ts]: https://docs.bazel.build/versions/master/be/general.html#test_suite
1123
1124 Args:
1125 name (str): The name of the `test_suite`.
1126 srcs (list): All test sources, typically `glob(["tests/**/*.rs"])`.
1127 **kwargs (dict): Additional keyword arguments for the underyling [rust_test](#rust_test) targets. The
1128 `tags` argument is also passed to the generated `test_suite` target.
1129 """
1130 tests = []
1131
1132 for src in srcs:
1133 if not src.endswith(".rs"):
1134 fail("srcs should have `.rs` extensions")
1135
1136 # Prefixed with `name` to allow parameterization with macros
1137 # The test name should not end with `.rs`
1138 test_name = name + "_" + src[:-3]
1139 rust_test(
1140 name = test_name,
1141 crate_name = test_name.replace("/", "_"),
1142 srcs = [src],
1143 **kwargs
1144 )
1145 tests.append(test_name)
1146
1147 native.test_suite(
1148 name = name,
1149 tests = tests,
1150 tags = kwargs.get("tags", None),
1151 )