blob: 571813b38b85cdaef2e0df656b96a0eca8fd1116 [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
Brian Silverman5f6f2762022-08-13 19:30:05 -070016load("@bazel_skylib//lib:paths.bzl", "paths")
Brian Silvermancc09f182022-03-09 15:40:20 -080017load("//rust/private:common.bzl", "rust_common")
18load("//rust/private:rustc.bzl", "rustc_compile_action")
19load(
20 "//rust/private:utils.bzl",
Brian Silverman5f6f2762022-08-13 19:30:05 -070021 "can_build_metadata",
Brian Silvermancc09f182022-03-09 15:40:20 -080022 "compute_crate_name",
23 "dedent",
24 "determine_output_hash",
25 "expand_dict_value_locations",
26 "find_toolchain",
27 "get_import_macro_deps",
28 "transform_deps",
29)
Brian Silvermancc09f182022-03-09 15:40:20 -080030# TODO(marco): Separate each rule into its own file.
31
32def _assert_no_deprecated_attributes(_ctx):
33 """Forces a failure if any deprecated attributes were specified
34
35 Args:
36 _ctx (ctx): The current rule's context object
37 """
38 pass
39
40def _assert_correct_dep_mapping(ctx):
41 """Forces a failure if proc_macro_deps and deps are mixed inappropriately
42
43 Args:
44 ctx (ctx): The current rule's context object
45 """
46 for dep in ctx.attr.deps:
47 if rust_common.crate_info in dep:
48 if dep[rust_common.crate_info].type == "proc-macro":
49 fail(
50 "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
51 ctx.label,
52 dep.label,
53 ),
54 )
55 for dep in ctx.attr.proc_macro_deps:
56 type = dep[rust_common.crate_info].type
57 if type != "proc-macro":
58 fail(
59 "{} listed {} in its proc_macro_deps, but it is not proc-macro, it is a {}. It should probably instead be listed in deps.".format(
60 ctx.label,
61 dep.label,
62 type,
63 ),
64 )
65
66def _determine_lib_name(name, crate_type, toolchain, lib_hash = None):
67 """See https://github.com/bazelbuild/rules_rust/issues/405
68
69 Args:
70 name (str): The name of the current target
71 crate_type (str): The `crate_type`
72 toolchain (rust_toolchain): The current `rust_toolchain`
73 lib_hash (str, optional): The hashed crate root path
74
75 Returns:
76 str: A unique library name
77 """
78 extension = None
79 prefix = ""
80 if crate_type in ("dylib", "cdylib", "proc-macro"):
81 extension = toolchain.dylib_ext
82 elif crate_type == "staticlib":
83 extension = toolchain.staticlib_ext
84 elif crate_type in ("lib", "rlib"):
85 # All platforms produce 'rlib' here
86 extension = ".rlib"
87 prefix = "lib"
88 elif crate_type == "bin":
89 fail("crate_type of 'bin' was detected in a rust_library. Please compile " +
90 "this crate as a rust_binary instead.")
91
92 if not extension:
93 fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " +
94 "please file an issue!").format(crate_type))
95
96 prefix = "lib"
97 if (toolchain.target_triple.find("windows") != -1) and crate_type not in ("lib", "rlib"):
98 prefix = ""
99 if toolchain.target_arch == "wasm32" and crate_type == "cdylib":
100 prefix = ""
101
102 return "{prefix}{name}{lib_hash}{extension}".format(
103 prefix = prefix,
104 name = name,
105 lib_hash = "-" + lib_hash if lib_hash else "",
106 extension = extension,
107 )
108
Brian Silverman5f6f2762022-08-13 19:30:05 -0700109def get_edition(attr, toolchain, label):
Brian Silvermancc09f182022-03-09 15:40:20 -0800110 """Returns the Rust edition from either the current rule's attirbutes or the current `rust_toolchain`
111
112 Args:
113 attr (struct): The current rule's attributes
114 toolchain (rust_toolchain): The `rust_toolchain` for the current target
Brian Silverman5f6f2762022-08-13 19:30:05 -0700115 label (Label): The label of the target being built
Brian Silvermancc09f182022-03-09 15:40:20 -0800116
117 Returns:
118 str: The target Rust edition
119 """
120 if getattr(attr, "edition"):
121 return attr.edition
Brian Silverman5f6f2762022-08-13 19:30:05 -0700122 elif not toolchain.default_edition:
123 fail("Attribute `edition` is required for {}.".format(label))
Brian Silvermancc09f182022-03-09 15:40:20 -0800124 else:
125 return toolchain.default_edition
126
Brian Silverman5f6f2762022-08-13 19:30:05 -0700127def _transform_sources(ctx, srcs, crate_root):
128 """Creates symlinks of the source files if needed.
129
130 Rustc assumes that the source files are located next to the crate root.
131 In case of a mix between generated and non-generated source files, this
132 we violate this assumption, as part of the sources will be located under
133 bazel-out/... . In order to allow for targets that contain both generated
134 and non-generated source files, we generate symlinks for all non-generated
135 files.
Brian Silvermancc09f182022-03-09 15:40:20 -0800136
137 Args:
Brian Silverman5f6f2762022-08-13 19:30:05 -0700138 ctx (struct): The current rule's context.
139 srcs (List[File]): The sources listed in the `srcs` attribute
140 crate_root (File): The file specified in the `crate_root` attribute,
141 if it exists, otherwise None
142
143 Returns:
144 Tuple(List[File], File): The transformed srcs and crate_root
145 """
146 has_generated_sources = len([src for src in srcs if not src.is_source]) > 0
147
148 if not has_generated_sources:
149 return srcs, crate_root
150
151 generated_sources = []
152
153 generated_root = crate_root
154
155 if crate_root and (crate_root.is_source or crate_root.root.path != ctx.bin_dir.path):
156 generated_root = ctx.actions.declare_file(crate_root.basename)
157 ctx.actions.symlink(
158 output = generated_root,
159 target_file = crate_root,
160 progress_message = "Creating symlink to source file: {}".format(crate_root.path),
161 )
162 if generated_root:
163 generated_sources.append(generated_root)
164
165 for src in srcs:
166 # We took care of the crate root above.
167 if src == crate_root:
168 continue
169 if src.is_source or src.root.path != ctx.bin_dir.path:
170 src_symlink = ctx.actions.declare_file(src.basename)
171 ctx.actions.symlink(
172 output = src_symlink,
173 target_file = src,
174 progress_message = "Creating symlink to source file: {}".format(src.path),
175 )
176 generated_sources.append(src_symlink)
177 else:
178 generated_sources.append(src)
179
180 return generated_sources, generated_root
181
182def crate_root_src(name, srcs, crate_type):
183 """Determines the source file for the crate root, should it not be specified in `attr.crate_root`.
184
185 Args:
186 name (str): The name of the target.
Brian Silvermancc09f182022-03-09 15:40:20 -0800187 srcs (list): A list of all sources for the target Crate.
188 crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
189
190 Returns:
191 File: The root File object for a given crate. See the following links for more details:
192 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library
193 - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
194 """
195 default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
196
Brian Silverman5f6f2762022-08-13 19:30:05 -0700197 crate_root = (
198 (srcs[0] if len(srcs) == 1 else None) or
199 _shortest_src_with_basename(srcs, default_crate_root_filename) or
200 _shortest_src_with_basename(srcs, name + ".rs")
201 )
Brian Silvermancc09f182022-03-09 15:40:20 -0800202 if not crate_root:
Brian Silverman5f6f2762022-08-13 19:30:05 -0700203 file_names = [default_crate_root_filename, name + ".rs"]
Brian Silvermancc09f182022-03-09 15:40:20 -0800204 fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
205 return crate_root
206
207def _shortest_src_with_basename(srcs, basename):
208 """Finds the shortest among the paths in srcs that match the desired basename.
209
210 Args:
211 srcs (list): A list of File objects
212 basename (str): The target basename to match against.
213
214 Returns:
215 File: The File object with the shortest path that matches `basename`
216 """
217 shortest = None
218 for f in srcs:
219 if f.basename == basename:
220 if not shortest or len(f.dirname) < len(shortest.dirname):
221 shortest = f
222 return shortest
223
224def _rust_library_impl(ctx):
225 """The implementation of the `rust_library` rule.
226
227 This rule provides CcInfo, so it can be used everywhere Bazel
228 expects rules_cc, but care must be taken to have the correct
229 dependencies on an allocator and std implemetation as needed.
230
231 Args:
232 ctx (ctx): The rule's context object
233
234 Returns:
235 list: A list of providers.
236 """
237 return _rust_library_common(ctx, "rlib")
238
239def _rust_static_library_impl(ctx):
240 """The implementation of the `rust_static_library` rule.
241
242 This rule provides CcInfo, so it can be used everywhere Bazel
243 expects rules_cc.
244
245 Args:
246 ctx (ctx): The rule's context object
247
248 Returns:
249 list: A list of providers.
250 """
251 return _rust_library_common(ctx, "staticlib")
252
253def _rust_shared_library_impl(ctx):
254 """The implementation of the `rust_shared_library` rule.
255
256 This rule provides CcInfo, so it can be used everywhere Bazel
257 expects rules_cc.
258
259 On Windows, a PDB file containing debugging information is available under
260 the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
261 is available under the key `dsym_folder` in `OutputGroupInfo`.
262
263 Args:
264 ctx (ctx): The rule's context object
265
266 Returns:
267 list: A list of providers.
268 """
269 return _rust_library_common(ctx, "cdylib")
270
271def _rust_proc_macro_impl(ctx):
272 """The implementation of the `rust_proc_macro` rule.
273
274 Args:
275 ctx (ctx): The rule's context object
276
277 Returns:
278 list: A list of providers.
279 """
280 return _rust_library_common(ctx, "proc-macro")
281
282def _rust_library_common(ctx, crate_type):
283 """The common implementation of the library-like rules.
284
285 Args:
286 ctx (ctx): The rule's context object
287 crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro
288
289 Returns:
290 list: A list of providers. See `rustc_compile_action`
291 """
292
Brian Silverman5f6f2762022-08-13 19:30:05 -0700293 srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
294 if not crate_root:
295 crate_root = crate_root_src(ctx.attr.name, srcs, "lib")
Brian Silvermancc09f182022-03-09 15:40:20 -0800296 _assert_no_deprecated_attributes(ctx)
297 _assert_correct_dep_mapping(ctx)
298
299 toolchain = find_toolchain(ctx)
300
301 # Determine unique hash for this rlib.
Brian Silverman5f6f2762022-08-13 19:30:05 -0700302 # Note that we don't include a hash for `cdylib` and `staticlib` since they are meant to be consumed externally
303 # and having a deterministic name is important since it ends up embedded in the executable. This is problematic
304 # when one needs to include the library with a specific filename into a larger application.
Brian Silvermancc09f182022-03-09 15:40:20 -0800305 # (see https://github.com/bazelbuild/rules_rust/issues/405#issuecomment-993089889 for more details)
Brian Silverman5f6f2762022-08-13 19:30:05 -0700306 if crate_type in ["cdylib", "staticlib"]:
Brian Silvermancc09f182022-03-09 15:40:20 -0800307 output_hash = None
Brian Silverman5f6f2762022-08-13 19:30:05 -0700308 else:
309 output_hash = determine_output_hash(crate_root, ctx.label)
Brian Silvermancc09f182022-03-09 15:40:20 -0800310
311 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
312 rust_lib_name = _determine_lib_name(
313 crate_name,
314 crate_type,
315 toolchain,
316 output_hash,
317 )
318 rust_lib = ctx.actions.declare_file(rust_lib_name)
319
Brian Silverman5f6f2762022-08-13 19:30:05 -0700320 rust_metadata = None
321 if can_build_metadata(toolchain, ctx, crate_type) and not ctx.attr.disable_pipelining:
322 rust_metadata = ctx.actions.declare_file(
323 paths.replace_extension(rust_lib_name, ".rmeta"),
324 sibling = rust_lib,
325 )
326
Brian Silvermancc09f182022-03-09 15:40:20 -0800327 deps = transform_deps(ctx.attr.deps)
328 proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
329
330 return rustc_compile_action(
331 ctx = ctx,
332 attr = ctx.attr,
333 toolchain = toolchain,
334 crate_info = rust_common.create_crate_info(
335 name = crate_name,
336 type = crate_type,
337 root = crate_root,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700338 srcs = depset(srcs),
Brian Silvermancc09f182022-03-09 15:40:20 -0800339 deps = depset(deps),
340 proc_macro_deps = depset(proc_macro_deps),
341 aliases = ctx.attr.aliases,
342 output = rust_lib,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700343 metadata = rust_metadata,
344 edition = get_edition(ctx.attr, toolchain, ctx.label),
Brian Silvermancc09f182022-03-09 15:40:20 -0800345 rustc_env = ctx.attr.rustc_env,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700346 rustc_env_files = ctx.files.rustc_env_files,
Brian Silvermancc09f182022-03-09 15:40:20 -0800347 is_test = False,
348 compile_data = depset(ctx.files.compile_data),
349 owner = ctx.label,
350 ),
351 output_hash = output_hash,
352 )
353
354def _rust_binary_impl(ctx):
355 """The implementation of the `rust_binary` rule
356
357 Args:
358 ctx (ctx): The rule's context object
359
360 Returns:
361 list: A list of providers. See `rustc_compile_action`
362 """
363 toolchain = find_toolchain(ctx)
364 crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
365 _assert_correct_dep_mapping(ctx)
366
367 output = ctx.actions.declare_file(ctx.label.name + toolchain.binary_ext)
368
369 deps = transform_deps(ctx.attr.deps)
370 proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
371
Brian Silverman5f6f2762022-08-13 19:30:05 -0700372 srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
373 if not crate_root:
374 crate_root = crate_root_src(ctx.attr.name, srcs, ctx.attr.crate_type)
375
Brian Silvermancc09f182022-03-09 15:40:20 -0800376 return rustc_compile_action(
377 ctx = ctx,
378 attr = ctx.attr,
379 toolchain = toolchain,
380 crate_info = rust_common.create_crate_info(
381 name = crate_name,
382 type = ctx.attr.crate_type,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700383 root = crate_root,
384 srcs = depset(srcs),
Brian Silvermancc09f182022-03-09 15:40:20 -0800385 deps = depset(deps),
386 proc_macro_deps = depset(proc_macro_deps),
387 aliases = ctx.attr.aliases,
388 output = output,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700389 edition = get_edition(ctx.attr, toolchain, ctx.label),
Brian Silvermancc09f182022-03-09 15:40:20 -0800390 rustc_env = ctx.attr.rustc_env,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700391 rustc_env_files = ctx.files.rustc_env_files,
Brian Silvermancc09f182022-03-09 15:40:20 -0800392 is_test = False,
393 compile_data = depset(ctx.files.compile_data),
394 owner = ctx.label,
395 ),
396 )
397
Brian Silverman5f6f2762022-08-13 19:30:05 -0700398def _rust_test_impl(ctx):
399 """The implementation of the `rust_test` rule.
Brian Silvermancc09f182022-03-09 15:40:20 -0800400
401 Args:
402 ctx (ctx): The ctx object for the current target.
Brian Silvermancc09f182022-03-09 15:40:20 -0800403
404 Returns:
405 list: The list of providers. See `rustc_compile_action`
406 """
407 _assert_no_deprecated_attributes(ctx)
408 _assert_correct_dep_mapping(ctx)
409
Brian Silverman5f6f2762022-08-13 19:30:05 -0700410 toolchain = find_toolchain(ctx)
411
412 srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
Brian Silvermancc09f182022-03-09 15:40:20 -0800413 crate_type = "bin"
414
415 deps = transform_deps(ctx.attr.deps)
416 proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
417
418 if ctx.attr.crate:
419 # Target is building the crate in `test` config
Brian Silverman5f6f2762022-08-13 19:30:05 -0700420 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
421
422 output_hash = determine_output_hash(crate.root, ctx.label)
423 output = ctx.actions.declare_file(
424 "test-%s/%s%s" % (
425 output_hash,
426 ctx.label.name,
427 toolchain.binary_ext,
428 ),
429 )
Brian Silvermancc09f182022-03-09 15:40:20 -0800430
431 # Optionally join compile data
432 if crate.compile_data:
433 compile_data = depset(ctx.files.compile_data, transitive = [crate.compile_data])
434 else:
435 compile_data = depset(ctx.files.compile_data)
Brian Silverman5f6f2762022-08-13 19:30:05 -0700436 rustc_env_files = ctx.files.rustc_env_files + crate.rustc_env_files
437 rustc_env = dict(crate.rustc_env)
438 rustc_env.update(**ctx.attr.rustc_env)
Brian Silvermancc09f182022-03-09 15:40:20 -0800439
440 # Build the test binary using the dependency's srcs.
441 crate_info = rust_common.create_crate_info(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700442 name = crate.name,
Brian Silvermancc09f182022-03-09 15:40:20 -0800443 type = crate_type,
444 root = crate.root,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700445 srcs = depset(srcs, transitive = [crate.srcs]),
Brian Silvermancc09f182022-03-09 15:40:20 -0800446 deps = depset(deps, transitive = [crate.deps]),
447 proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
448 aliases = ctx.attr.aliases,
449 output = output,
450 edition = crate.edition,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700451 rustc_env = rustc_env,
452 rustc_env_files = rustc_env_files,
Brian Silvermancc09f182022-03-09 15:40:20 -0800453 is_test = True,
454 compile_data = compile_data,
455 wrapped_crate_type = crate.type,
456 owner = ctx.label,
457 )
458 else:
Brian Silverman5f6f2762022-08-13 19:30:05 -0700459 if not crate_root:
460 crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, "lib")
461
462 output_hash = determine_output_hash(crate_root, ctx.label)
463 output = ctx.actions.declare_file(
464 "test-%s/%s%s" % (
465 output_hash,
466 ctx.label.name,
467 toolchain.binary_ext,
468 ),
469 )
470
Brian Silvermancc09f182022-03-09 15:40:20 -0800471 # Target is a standalone crate. Build the test binary as its own crate.
472 crate_info = rust_common.create_crate_info(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700473 name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name),
Brian Silvermancc09f182022-03-09 15:40:20 -0800474 type = crate_type,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700475 root = crate_root,
476 srcs = depset(srcs),
Brian Silvermancc09f182022-03-09 15:40:20 -0800477 deps = depset(deps),
478 proc_macro_deps = depset(proc_macro_deps),
479 aliases = ctx.attr.aliases,
480 output = output,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700481 edition = get_edition(ctx.attr, toolchain, ctx.label),
Brian Silvermancc09f182022-03-09 15:40:20 -0800482 rustc_env = ctx.attr.rustc_env,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700483 rustc_env_files = ctx.files.rustc_env_files,
Brian Silvermancc09f182022-03-09 15:40:20 -0800484 is_test = True,
485 compile_data = depset(ctx.files.compile_data),
486 owner = ctx.label,
487 )
488
489 providers = rustc_compile_action(
490 ctx = ctx,
491 attr = ctx.attr,
492 toolchain = toolchain,
493 crate_info = crate_info,
494 rust_flags = ["--test"] if ctx.attr.use_libtest_harness else ["--cfg", "test"],
495 )
496 data = getattr(ctx.attr, "data", [])
497
498 env = expand_dict_value_locations(
499 ctx,
500 getattr(ctx.attr, "env", {}),
501 data,
502 )
Brian Silverman5f6f2762022-08-13 19:30:05 -0700503 if toolchain.llvm_cov and ctx.configuration.coverage_enabled:
504 if not toolchain.llvm_profdata:
505 fail("toolchain.llvm_profdata is required if toolchain.llvm_cov is set.")
506
507 env["RUST_LLVM_COV"] = toolchain.llvm_cov.path
508 env["RUST_LLVM_PROFDATA"] = toolchain.llvm_profdata.path
Brian Silvermancc09f182022-03-09 15:40:20 -0800509 providers.append(testing.TestEnvironment(env))
510
511 return providers
512
Brian Silverman5f6f2762022-08-13 19:30:05 -0700513def _stamp_attribute(default_value):
514 return attr.int(
515 doc = dedent("""\
516 Whether to encode build information into the `Rustc` action. Possible values:
Brian Silvermancc09f182022-03-09 15:40:20 -0800517
Brian Silverman5f6f2762022-08-13 19:30:05 -0700518 - `stamp = 1`: Always stamp the build information into the `Rustc` action, even in \
519 [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \
520 This setting should be avoided, since it potentially kills remote caching for the target and \
521 any downstream actions that depend on it.
Brian Silvermancc09f182022-03-09 15:40:20 -0800522
Brian Silverman5f6f2762022-08-13 19:30:05 -0700523 - `stamp = 0`: Always replace build information by constant values. This gives good build result caching.
Brian Silvermancc09f182022-03-09 15:40:20 -0800524
Brian Silverman5f6f2762022-08-13 19:30:05 -0700525 - `stamp = -1`: Embedding of build information is controlled by the \
526 [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
527
528 Stamped targets are not rebuilt unless their dependencies change.
529
530 For example if a `rust_library` is stamped, and a `rust_binary` depends on that library, the stamped
531 library won't be rebuilt when we change sources of the `rust_binary`. This is different from how
532 [`cc_library.linkstamps`](https://docs.bazel.build/versions/main/be/c-cpp.html#cc_library.linkstamp)
533 behaves.
534 """),
535 default = default_value,
536 values = [1, 0, -1],
Brian Silvermancc09f182022-03-09 15:40:20 -0800537 )
538
Brian Silvermancc09f182022-03-09 15:40:20 -0800539_common_attrs = {
540 "aliases": attr.label_keyed_string_dict(
541 doc = dedent("""\
542 Remap crates to a new name or moniker for linkage to this target
543
544 These are other `rust_library` targets and will be presented as the new name given.
545 """),
546 ),
547 "compile_data": attr.label_list(
548 doc = dedent("""\
549 List of files used by this rule at compile time.
550
551 This attribute can be used to specify any data files that are embedded into
552 the library, such as via the
553 [`include_str!`](https://doc.rust-lang.org/std/macro.include_str!.html)
554 macro.
555 """),
556 allow_files = True,
557 ),
558 "crate_features": attr.string_list(
559 doc = dedent("""\
560 List of features to enable for this crate.
561
562 Features are defined in the code using the `#[cfg(feature = "foo")]`
563 configuration option. The features listed here will be passed to `rustc`
564 with `--cfg feature="${feature_name}"` flags.
565 """),
566 ),
567 "crate_name": attr.string(
568 doc = dedent("""\
569 Crate name to use for this target.
570
571 This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores.
572 Defaults to the target name, with any hyphens replaced by underscores.
573 """),
574 ),
575 "crate_root": attr.label(
576 doc = dedent("""\
577 The file that will be passed to `rustc` to be used for building this crate.
578
579 If `crate_root` is not set, then this rule will look for a `lib.rs` file (or `main.rs` for rust_binary)
580 or the single file in `srcs` if `srcs` contains only one file.
581 """),
582 allow_single_file = [".rs"],
583 ),
584 "data": attr.label_list(
585 doc = dedent("""\
586 List of files used by this rule at compile time and runtime.
587
588 If including data at compile time with include_str!() and similar,
589 prefer `compile_data` over `data`, to prevent the data also being included
590 in the runfiles.
591 """),
592 allow_files = True,
593 ),
594 "deps": attr.label_list(
595 doc = dedent("""\
596 List of other libraries to be linked to this library target.
597
598 These can be either other `rust_library` targets or `cc_library` targets if
599 linking a native library.
600 """),
601 ),
602 "edition": attr.string(
603 doc = "The rust edition to use for this crate. Defaults to the edition specified in the rust_toolchain.",
604 ),
605 # Previously `proc_macro_deps` were a part of `deps`, and then proc_macro_host_transition was
606 # used into cfg="host" using `@local_config_platform//:host`.
607 # This fails for remote execution, which needs cfg="exec", and there isn't anything like
608 # `@local_config_platform//:exec` exposed.
609 "proc_macro_deps": attr.label_list(
610 doc = dedent("""\
611 List of `rust_library` targets with kind `proc-macro` used to help build this library target.
612 """),
613 cfg = "exec",
614 providers = [rust_common.crate_info],
615 ),
616 "rustc_env": attr.string_dict(
617 doc = dedent("""\
618 Dictionary of additional `"key": "value"` environment variables to set for rustc.
619
620 rust_test()/rust_binary() rules can use $(rootpath //package:target) to pass in the
621 location of a generated file or external tool. Cargo build scripts that wish to
622 expand locations should use cargo_build_script()'s build_script_env argument instead,
623 as build scripts are run in a different environment - see cargo_build_script()'s
624 documentation for more.
625 """),
626 ),
627 "rustc_env_files": attr.label_list(
628 doc = dedent("""\
629 Files containing additional environment variables to set for rustc.
630
631 These files should contain a single variable per line, of format
632 `NAME=value`, and newlines may be included in a value by ending a
633 line with a trailing back-slash (`\\\\`).
634
635 The order that these files will be processed is unspecified, so
636 multiple definitions of a particular variable are discouraged.
637
638 Note that the variables here are subject to
639 [workspace status](https://docs.bazel.build/versions/main/user-manual.html#workspace_status)
640 stamping should the `stamp` attribute be enabled. Stamp variables
641 should be wrapped in brackets in order to be resolved. E.g.
642 `NAME={WORKSPACE_STATUS_VARIABLE}`.
643 """),
644 allow_files = True,
645 ),
646 "rustc_flags": attr.string_list(
647 doc = dedent("""\
648 List of compiler flags passed to `rustc`.
649
650 These strings are subject to Make variable expansion for predefined
651 source/output path variables like `$location`, `$execpath`, and
652 `$rootpath`. This expansion is useful if you wish to pass a generated
653 file of arguments to rustc: `@$(location //package:target)`.
654 """),
655 ),
656 # TODO(stardoc): How do we provide additional documentation to an inherited attribute?
657 # "name": attr.string(
658 # doc = "This name will also be used as the name of the crate built by this rule.",
659 # `),
660 "srcs": attr.label_list(
661 doc = dedent("""\
662 List of Rust `.rs` source files used to build the library.
663
664 If `srcs` contains more than one file, then there must be a file either
665 named `lib.rs`. Otherwise, `crate_root` must be set to the source file that
666 is the root of the crate to be passed to rustc to build this crate.
667 """),
668 allow_files = [".rs"],
669 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700670 "stamp": _stamp_attribute(default_value = 0),
Brian Silvermancc09f182022-03-09 15:40:20 -0800671 "version": attr.string(
672 doc = "A version to inject in the cargo environment variable.",
673 default = "0.0.0",
674 ),
675 "_cc_toolchain": attr.label(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700676 doc = (
677 "In order to use find_cc_toolchain, your rule has to depend " +
678 "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
679 "docs for details."
680 ),
681 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
Brian Silvermancc09f182022-03-09 15:40:20 -0800682 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700683 "_collect_cc_coverage": attr.label(
684 default = Label("//util:collect_coverage"),
685 executable = True,
686 cfg = "exec",
687 ),
688 "_error_format": attr.label(
689 default = Label("//:error_format"),
690 ),
691 "_extra_exec_rustc_flag": attr.label(
692 default = Label("//:extra_exec_rustc_flag"),
693 ),
694 "_extra_exec_rustc_flags": attr.label(
695 default = Label("//:extra_exec_rustc_flags"),
696 ),
697 "_extra_rustc_flag": attr.label(
698 default = Label("//:extra_rustc_flag"),
699 ),
700 "_extra_rustc_flags": attr.label(
701 default = Label("//:extra_rustc_flags"),
702 ),
Brian Silvermancc09f182022-03-09 15:40:20 -0800703 "_import_macro_dep": attr.label(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700704 default = Label("//util/import"),
705 cfg = "exec",
706 ),
707 "_is_proc_macro_dep": attr.label(
708 default = Label("//:is_proc_macro_dep"),
709 ),
710 "_is_proc_macro_dep_enabled": attr.label(
711 default = Label("//:is_proc_macro_dep_enabled"),
Brian Silvermancc09f182022-03-09 15:40:20 -0800712 ),
713 "_process_wrapper": attr.label(
Brian Silverman5f6f2762022-08-13 19:30:05 -0700714 doc = "A process wrapper for running rustc on all platforms.",
Brian Silvermancc09f182022-03-09 15:40:20 -0800715 default = Label("//util/process_wrapper"),
716 executable = True,
717 allow_single_file = True,
718 cfg = "exec",
719 ),
720 "_stamp_flag": attr.label(
721 doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
722 default = Label("//rust/private:stamp"),
723 ),
724}
725
Brian Silverman5f6f2762022-08-13 19:30:05 -0700726_experimental_use_cc_common_link_attrs = {
727 "experimental_use_cc_common_link": attr.int(
728 doc = (
729 "Whether to use cc_common.link to link rust binaries. " +
730 "Possible values: [-1, 0, 1]. " +
731 "-1 means use the value of the toolchain.experimental_use_cc_common_link " +
732 "boolean build setting to determine. " +
733 "0 means do not use cc_common.link (use rustc instead). " +
734 "1 means use cc_common.link."
735 ),
736 values = [-1, 0, 1],
737 default = -1,
738 ),
739}
740
741_rust_test_attrs = dict({
Brian Silvermancc09f182022-03-09 15:40:20 -0800742 "crate": attr.label(
743 mandatory = False,
744 doc = dedent("""\
745 Target inline tests declared in the given crate
746
747 These tests are typically those that would be held out under
748 `#[cfg(test)]` declarations.
749 """),
750 ),
751 "env": attr.string_dict(
752 mandatory = False,
753 doc = dedent("""\
754 Specifies additional environment variables to set when the test is executed by bazel test.
755 Values are subject to `$(rootpath)`, `$(execpath)`, location, and
756 ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution.
757
758 Execpath returns absolute path, and in order to be able to construct the absolute path we
759 need to wrap the test binary in a launcher. Using a launcher comes with complications, such as
760 more complicated debugger attachment.
761 """),
762 ),
763 "use_libtest_harness": attr.bool(
764 mandatory = False,
765 default = True,
766 doc = dedent("""\
767 Whether to use `libtest`. For targets using this flag, individual tests can be run by using the
768 [--test_arg](https://docs.bazel.build/versions/4.0.0/command-line-reference.html#flag--test_arg) flag.
769 E.g. `bazel test //src:rust_test --test_arg=foo::test::test_fn`.
770 """),
771 ),
772 "_grep_includes": attr.label(
773 allow_single_file = True,
774 cfg = "exec",
775 default = Label("@bazel_tools//tools/cpp:grep-includes"),
776 executable = True,
777 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700778}.items() + _experimental_use_cc_common_link_attrs.items())
Brian Silvermancc09f182022-03-09 15:40:20 -0800779
780_common_providers = [
781 rust_common.crate_info,
782 rust_common.dep_info,
783 DefaultInfo,
784]
785
786rust_library = rule(
787 implementation = _rust_library_impl,
788 provides = _common_providers,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700789 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()),
Brian Silvermancc09f182022-03-09 15:40:20 -0800799 fragments = ["cpp"],
800 host_fragments = ["cpp"],
801 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700802 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800803 "@bazel_tools//tools/cpp:toolchain_type",
804 ],
805 incompatible_use_toolchain_transition = True,
806 doc = dedent("""\
807 Builds a Rust library crate.
808
809 Example:
810
811 Suppose you have the following directory structure for a simple Rust library crate:
812
813 ```output
814 [workspace]/
815 WORKSPACE
816 hello_lib/
817 BUILD
818 src/
819 greeter.rs
820 lib.rs
821 ```
822
823 `hello_lib/src/greeter.rs`:
824 ```rust
825 pub struct Greeter {
826 greeting: String,
827 }
828
829 impl Greeter {
830 pub fn new(greeting: &str) -> Greeter {
831 Greeter { greeting: greeting.to_string(), }
832 }
833
834 pub fn greet(&self, thing: &str) {
835 println!("{} {}", &self.greeting, thing);
836 }
837 }
838 ```
839
840 `hello_lib/src/lib.rs`:
841
842 ```rust
843 pub mod greeter;
844 ```
845
846 `hello_lib/BUILD`:
847 ```python
848 package(default_visibility = ["//visibility:public"])
849
850 load("@rules_rust//rust:defs.bzl", "rust_library")
851
852 rust_library(
853 name = "hello_lib",
854 srcs = [
855 "src/greeter.rs",
856 "src/lib.rs",
857 ],
858 )
859 ```
860
861 Build the library:
862 ```output
863 $ bazel build //hello_lib
864 INFO: Found 1 target...
865 Target //examples/rust/hello_lib:hello_lib up-to-date:
866 bazel-bin/examples/rust/hello_lib/libhello_lib.rlib
867 INFO: Elapsed time: 1.245s, Critical Path: 1.01s
868 ```
869 """),
870)
871
872rust_static_library = rule(
873 implementation = _rust_static_library_impl,
Brian Silvermancc09f182022-03-09 15:40:20 -0800874 attrs = dict(_common_attrs.items()),
875 fragments = ["cpp"],
876 host_fragments = ["cpp"],
877 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700878 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800879 "@bazel_tools//tools/cpp:toolchain_type",
880 ],
881 incompatible_use_toolchain_transition = True,
882 doc = dedent("""\
883 Builds a Rust static library.
884
885 This static library will contain all transitively reachable crates and native objects.
886 It is meant to be used when producing an artifact that is then consumed by some other build system
887 (for example to produce an archive that Python program links against).
888
889 This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
890
891 When building the whole binary in Bazel, use `rust_library` instead.
892 """),
893)
894
895rust_shared_library = rule(
896 implementation = _rust_shared_library_impl,
Brian Silvermancc09f182022-03-09 15:40:20 -0800897 attrs = dict(_common_attrs.items()),
898 fragments = ["cpp"],
899 host_fragments = ["cpp"],
900 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700901 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800902 "@bazel_tools//tools/cpp:toolchain_type",
903 ],
904 incompatible_use_toolchain_transition = True,
905 doc = dedent("""\
906 Builds a Rust shared library.
907
908 This shared library will contain all transitively reachable crates and native objects.
909 It is meant to be used when producing an artifact that is then consumed by some other build system
910 (for example to produce a shared library that Python program links against).
911
912 This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
913
914 When building the whole binary in Bazel, use `rust_library` instead.
915 """),
916)
917
Brian Silverman5f6f2762022-08-13 19:30:05 -0700918def _proc_macro_dep_transition_impl(settings, _attr):
919 if settings["//:is_proc_macro_dep_enabled"]:
920 return {"//:is_proc_macro_dep": True}
921 else:
922 return []
923
924_proc_macro_dep_transition = transition(
925 inputs = ["//:is_proc_macro_dep_enabled"],
926 outputs = ["//:is_proc_macro_dep"],
927 implementation = _proc_macro_dep_transition_impl,
928)
929
Brian Silvermancc09f182022-03-09 15:40:20 -0800930rust_proc_macro = rule(
931 implementation = _rust_proc_macro_impl,
932 provides = _common_providers,
Brian Silverman5f6f2762022-08-13 19:30:05 -0700933 # Start by copying the common attributes, then override the `deps` attribute
934 # to apply `_proc_macro_dep_transition`. To add this transition we additionally
935 # need to declare `_allowlist_function_transition`, see
936 # https://docs.bazel.build/versions/main/skylark/config.html#user-defined-transitions.
937 attrs = dict(
938 _common_attrs.items(),
939 _allowlist_function_transition = attr.label(default = Label("//tools/allowlists/function_transition_allowlist")),
940 deps = attr.label_list(
941 doc = dedent("""\
942 List of other libraries to be linked to this library target.
943
944 These can be either other `rust_library` targets or `cc_library` targets if
945 linking a native library.
946 """),
947 cfg = _proc_macro_dep_transition,
948 ),
949 ),
Brian Silvermancc09f182022-03-09 15:40:20 -0800950 fragments = ["cpp"],
951 host_fragments = ["cpp"],
952 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -0700953 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -0800954 "@bazel_tools//tools/cpp:toolchain_type",
955 ],
956 incompatible_use_toolchain_transition = True,
957 doc = dedent("""\
958 Builds a Rust proc-macro crate.
959 """),
960)
961
Brian Silverman5f6f2762022-08-13 19:30:05 -0700962_rust_binary_attrs = dict({
Brian Silvermancc09f182022-03-09 15:40:20 -0800963 "crate_type": attr.string(
964 doc = dedent("""\
965 Crate type that will be passed to `rustc` to be used for building this crate.
966
967 This option is a temporary workaround and should be used only when building
968 for WebAssembly targets (//rust/platform:wasi and //rust/platform:wasm).
969 """),
970 default = "bin",
971 ),
972 "linker_script": attr.label(
973 doc = dedent("""\
974 Link script to forward into linker via rustc options.
975 """),
976 cfg = "exec",
977 allow_single_file = True,
978 ),
979 "out_binary": attr.bool(
980 doc = (
981 "Force a target, regardless of it's `crate_type`, to always mark the " +
982 "file as executable. This attribute is only used to support wasm targets but is " +
983 "expected to be removed following a resolution to https://github.com/bazelbuild/rules_rust/issues/771."
984 ),
985 default = False,
986 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700987 "stamp": _stamp_attribute(default_value = -1),
Brian Silvermancc09f182022-03-09 15:40:20 -0800988 "_grep_includes": attr.label(
989 allow_single_file = True,
990 cfg = "exec",
991 default = Label("@bazel_tools//tools/cpp:grep-includes"),
992 executable = True,
993 ),
Brian Silverman5f6f2762022-08-13 19:30:05 -0700994}.items() + _experimental_use_cc_common_link_attrs.items())
Brian Silvermancc09f182022-03-09 15:40:20 -0800995
996rust_binary = rule(
997 implementation = _rust_binary_impl,
998 provides = _common_providers,
999 attrs = dict(_common_attrs.items() + _rust_binary_attrs.items()),
1000 executable = True,
1001 fragments = ["cpp"],
1002 host_fragments = ["cpp"],
1003 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -07001004 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -08001005 "@bazel_tools//tools/cpp:toolchain_type",
1006 ],
1007 incompatible_use_toolchain_transition = True,
1008 doc = dedent("""\
1009 Builds a Rust binary crate.
1010
1011 Example:
1012
1013 Suppose you have the following directory structure for a Rust project with a
1014 library crate, `hello_lib`, and a binary crate, `hello_world` that uses the
1015 `hello_lib` library:
1016
1017 ```output
1018 [workspace]/
1019 WORKSPACE
1020 hello_lib/
1021 BUILD
1022 src/
1023 lib.rs
1024 hello_world/
1025 BUILD
1026 src/
1027 main.rs
1028 ```
1029
1030 `hello_lib/src/lib.rs`:
1031 ```rust
1032 pub struct Greeter {
1033 greeting: String,
1034 }
1035
1036 impl Greeter {
1037 pub fn new(greeting: &str) -> Greeter {
1038 Greeter { greeting: greeting.to_string(), }
1039 }
1040
1041 pub fn greet(&self, thing: &str) {
1042 println!("{} {}", &self.greeting, thing);
1043 }
1044 }
1045 ```
1046
1047 `hello_lib/BUILD`:
1048 ```python
1049 package(default_visibility = ["//visibility:public"])
1050
1051 load("@rules_rust//rust:defs.bzl", "rust_library")
1052
1053 rust_library(
1054 name = "hello_lib",
1055 srcs = ["src/lib.rs"],
1056 )
1057 ```
1058
1059 `hello_world/src/main.rs`:
1060 ```rust
1061 extern crate hello_lib;
1062
1063 fn main() {
1064 let hello = hello_lib::Greeter::new("Hello");
1065 hello.greet("world");
1066 }
1067 ```
1068
1069 `hello_world/BUILD`:
1070 ```python
1071 load("@rules_rust//rust:defs.bzl", "rust_binary")
1072
1073 rust_binary(
1074 name = "hello_world",
1075 srcs = ["src/main.rs"],
1076 deps = ["//hello_lib"],
1077 )
1078 ```
1079
1080 Build and run `hello_world`:
1081 ```
1082 $ bazel run //hello_world
1083 INFO: Found 1 target...
1084 Target //examples/rust/hello_world:hello_world up-to-date:
1085 bazel-bin/examples/rust/hello_world/hello_world
1086 INFO: Elapsed time: 1.308s, Critical Path: 1.22s
1087
1088 INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world
1089 Hello world
1090 ```
1091
1092 On Windows, a PDB file containing debugging information is available under
1093 the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
1094 is available under the key `dsym_folder` in `OutputGroupInfo`.
1095"""),
1096)
1097
Brian Silverman5f6f2762022-08-13 19:30:05 -07001098def _common_attrs_for_binary_without_process_wrapper(attrs):
1099 new_attr = dict(attrs)
1100
1101 # use a fake process wrapper
1102 new_attr["_process_wrapper"] = attr.label(
1103 default = None,
1104 executable = True,
1105 allow_single_file = True,
1106 cfg = "exec",
1107 )
1108
1109 # fix stamp = 0
1110 new_attr["stamp"] = attr.int(
1111 doc = dedent("""\
1112 Fix `stamp = 0` as stamping is not supported when building without process_wrapper:
1113 https://github.com/bazelbuild/rules_rust/blob/8df4517d370b0c543a01ba38b63e1d5a4104b035/rust/private/rustc.bzl#L955
1114 """),
1115 default = 0,
1116 values = [0],
1117 )
1118
1119 return new_attr
1120
1121# Provides an internal rust_{binary,library} to use that we can use to build the process
1122# wrapper, this breaks the dependency of rust_* on the process wrapper by
1123# setting it to None, which the functions in rustc detect and build accordingly.
1124rust_binary_without_process_wrapper = rule(
1125 implementation = _rust_binary_impl,
1126 provides = _common_providers,
1127 attrs = _common_attrs_for_binary_without_process_wrapper(_common_attrs.items() + _rust_binary_attrs.items()),
1128 executable = True,
1129 fragments = ["cpp"],
1130 host_fragments = ["cpp"],
1131 toolchains = [
1132 str(Label("//rust:toolchain_type")),
1133 "@bazel_tools//tools/cpp:toolchain_type",
1134 ],
1135 incompatible_use_toolchain_transition = True,
1136)
1137
1138rust_library_without_process_wrapper = rule(
1139 implementation = _rust_library_impl,
1140 provides = _common_providers,
1141 attrs = dict(_common_attrs_for_binary_without_process_wrapper(_common_attrs).items()),
1142 fragments = ["cpp"],
1143 host_fragments = ["cpp"],
1144 toolchains = [
1145 str(Label("//rust:toolchain_type")),
1146 "@bazel_tools//tools/cpp:toolchain_type",
1147 ],
1148 incompatible_use_toolchain_transition = True,
1149)
1150
Brian Silvermancc09f182022-03-09 15:40:20 -08001151rust_test = rule(
1152 implementation = _rust_test_impl,
1153 provides = _common_providers,
1154 attrs = dict(_common_attrs.items() +
1155 _rust_test_attrs.items()),
1156 executable = True,
1157 fragments = ["cpp"],
1158 host_fragments = ["cpp"],
1159 test = True,
1160 toolchains = [
Brian Silverman5f6f2762022-08-13 19:30:05 -07001161 str(Label("//rust:toolchain_type")),
Brian Silvermancc09f182022-03-09 15:40:20 -08001162 "@bazel_tools//tools/cpp:toolchain_type",
1163 ],
1164 incompatible_use_toolchain_transition = True,
1165 doc = dedent("""\
1166 Builds a Rust test crate.
1167
1168 Examples:
1169
1170 Suppose you have the following directory structure for a Rust library crate \
1171 with unit test code in the library sources:
1172
1173 ```output
1174 [workspace]/
1175 WORKSPACE
1176 hello_lib/
1177 BUILD
1178 src/
1179 lib.rs
1180 ```
1181
1182 `hello_lib/src/lib.rs`:
1183 ```rust
1184 pub struct Greeter {
1185 greeting: String,
1186 }
1187
1188 impl Greeter {
1189 pub fn new(greeting: &str) -> Greeter {
1190 Greeter { greeting: greeting.to_string(), }
1191 }
1192
1193 pub fn greet(&self, thing: &str) -> String {
1194 format!("{} {}", &self.greeting, thing)
1195 }
1196 }
1197
1198 #[cfg(test)]
1199 mod test {
1200 use super::Greeter;
1201
1202 #[test]
1203 fn test_greeting() {
1204 let hello = Greeter::new("Hi");
1205 assert_eq!("Hi Rust", hello.greet("Rust"));
1206 }
1207 }
1208 ```
1209
Brian Silverman5f6f2762022-08-13 19:30:05 -07001210 To build and run the tests, simply add a `rust_test` rule with no `srcs`
1211 and only depends on the `hello_lib` `rust_library` target via the
1212 `crate` attribute:
Brian Silvermancc09f182022-03-09 15:40:20 -08001213
1214 `hello_lib/BUILD`:
1215 ```python
1216 load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
1217
1218 rust_library(
1219 name = "hello_lib",
1220 srcs = ["src/lib.rs"],
1221 )
1222
1223 rust_test(
1224 name = "hello_lib_test",
Brian Silvermancc09f182022-03-09 15:40:20 -08001225 crate = ":hello_lib",
1226 # You may add other deps that are specific to the test configuration
1227 deps = ["//some/dev/dep"],
Brian Silvermancc09f182022-03-09 15:40:20 -08001228 ```
1229
Brian Silverman5f6f2762022-08-13 19:30:05 -07001230 Run the test with `bazel test //hello_lib:hello_lib_test`. The crate
1231 will be built using the same crate name as the underlying ":hello_lib"
1232 crate.
1233
Brian Silvermancc09f182022-03-09 15:40:20 -08001234 ### Example: `test` directory
1235
1236 Integration tests that live in the [`tests` directory][int-tests], they are \
1237 essentially built as separate crates. Suppose you have the following directory \
1238 structure where `greeting.rs` is an integration test for the `hello_lib` \
1239 library crate:
1240
1241 [int-tests]: http://doc.rust-lang.org/book/testing.html#the-tests-directory
1242
1243 ```output
1244 [workspace]/
1245 WORKSPACE
1246 hello_lib/
1247 BUILD
1248 src/
1249 lib.rs
1250 tests/
1251 greeting.rs
1252 ```
1253
1254 `hello_lib/tests/greeting.rs`:
1255 ```rust
1256 extern crate hello_lib;
1257
1258 use hello_lib;
1259
1260 #[test]
1261 fn test_greeting() {
1262 let hello = greeter::Greeter::new("Hello");
1263 assert_eq!("Hello world", hello.greeting("world"));
1264 }
1265 ```
1266
1267 To build the `greeting.rs` integration test, simply add a `rust_test` target
1268 with `greeting.rs` in `srcs` and a dependency on the `hello_lib` target:
1269
1270 `hello_lib/BUILD`:
1271 ```python
1272 load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
1273
1274 rust_library(
1275 name = "hello_lib",
1276 srcs = ["src/lib.rs"],
1277 )
1278
1279 rust_test(
1280 name = "greeting_test",
1281 srcs = ["tests/greeting.rs"],
1282 deps = [":hello_lib"],
1283 )
1284 ```
1285
Brian Silverman5f6f2762022-08-13 19:30:05 -07001286 Run the test with `bazel test //hello_lib:greeting_test`.
Brian Silvermancc09f182022-03-09 15:40:20 -08001287"""),
1288)
1289
1290def rust_test_suite(name, srcs, **kwargs):
1291 """A rule for creating a test suite for a set of `rust_test` targets.
1292
1293 This rule can be used for setting up typical rust [integration tests][it]. Given the following
1294 directory structure:
1295
1296 ```text
1297 [crate]/
1298 BUILD.bazel
1299 src/
1300 lib.rs
1301 main.rs
1302 tests/
1303 integrated_test_a.rs
1304 integrated_test_b.rs
1305 integrated_test_c.rs
1306 patterns/
1307 fibonacci_test.rs
1308 ```
1309
1310 The rule can be used to generate [rust_test](#rust_test) targets for each source file under `tests`
1311 and a [test_suite][ts] which encapsulates all tests.
1312
1313 ```python
1314 load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test_suite")
1315
1316 rust_library(
1317 name = "math_lib",
1318 srcs = ["src/lib.rs"],
1319 )
1320
1321 rust_binary(
1322 name = "math_bin",
1323 srcs = ["src/main.rs"],
1324 )
1325
1326 rust_test_suite(
1327 name = "integrated_tests_suite",
1328 srcs = glob(["tests/**"]),
1329 deps = [":math_lib"],
1330 )
1331 ```
1332
1333 [it]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
1334 [ts]: https://docs.bazel.build/versions/master/be/general.html#test_suite
1335
1336 Args:
1337 name (str): The name of the `test_suite`.
1338 srcs (list): All test sources, typically `glob(["tests/**/*.rs"])`.
1339 **kwargs (dict): Additional keyword arguments for the underyling [rust_test](#rust_test) targets. The
1340 `tags` argument is also passed to the generated `test_suite` target.
1341 """
1342 tests = []
1343
1344 for src in srcs:
1345 if not src.endswith(".rs"):
1346 fail("srcs should have `.rs` extensions")
1347
1348 # Prefixed with `name` to allow parameterization with macros
1349 # The test name should not end with `.rs`
1350 test_name = name + "_" + src[:-3]
1351 rust_test(
1352 name = test_name,
Brian Silvermancc09f182022-03-09 15:40:20 -08001353 srcs = [src],
1354 **kwargs
1355 )
1356 tests.append(test_name)
1357
1358 native.test_suite(
1359 name = name,
1360 tests = tests,
1361 tags = kwargs.get("tags", None),
1362 )