Squashed 'third_party/rules_rust/' content from commit bf59038ca
git-subtree-dir: third_party/rules_rust
git-subtree-split: bf59038cac11798cbaef9f3bf965bad8182b97fa
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
Change-Id: I5a20e403203d670df467ea97dde9a4ac40339a8d
diff --git a/rust/private/BUILD.bazel b/rust/private/BUILD.bazel
new file mode 100644
index 0000000..929a39e
--- /dev/null
+++ b/rust/private/BUILD.bazel
@@ -0,0 +1,24 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("//rust/private:rust_analyzer.bzl", "rust_analyzer_detect_sysroot")
+load("//rust/private:stamp.bzl", "stamp_build_setting")
+
+bzl_library(
+ name = "bzl_lib",
+ srcs = glob(["**/*.bzl"]),
+ visibility = ["//rust:__subpackages__"],
+ deps = ["//rust/platform:bzl_lib"],
+)
+
+alias(
+ name = "rules",
+ actual = ":bzl_lib",
+ deprecation = "Please use the `@rules_rust//private:bzl_lib` target instead",
+ visibility = ["//rust:__subpackages__"],
+)
+
+stamp_build_setting(name = "stamp")
+
+rust_analyzer_detect_sysroot(
+ name = "rust_analyzer_detect_sysroot",
+ visibility = ["//visibility:public"],
+)
diff --git a/rust/private/README.md b/rust/private/README.md
new file mode 100644
index 0000000..a7a45c6
--- /dev/null
+++ b/rust/private/README.md
@@ -0,0 +1 @@
+Implementation details of the rules that are not publicly exposed.
\ No newline at end of file
diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl
new file mode 100644
index 0000000..d1464e9
--- /dev/null
+++ b/rust/private/clippy.bzl
@@ -0,0 +1,304 @@
+# Copyright 2020 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A module defining clippy rules"""
+
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:providers.bzl", "CaptureClippyOutputInfo", "ClippyInfo")
+load(
+ "//rust/private:rustc.bzl",
+ "collect_deps",
+ "collect_inputs",
+ "construct_arguments",
+)
+load(
+ "//rust/private:utils.bzl",
+ "determine_output_hash",
+ "find_cc_toolchain",
+ "find_toolchain",
+)
+
+def _get_clippy_ready_crate_info(target, aspect_ctx):
+ """Check that a target is suitable for clippy and extract the `CrateInfo` provider from it.
+
+ Args:
+ target (Target): The target the aspect is running on.
+ aspect_ctx (ctx, optional): The aspect's context object.
+
+ Returns:
+ CrateInfo, optional: A `CrateInfo` provider if clippy should be run or `None`.
+ """
+
+ # Ignore external targets
+ if target.label.workspace_root.startswith("external"):
+ return None
+
+ # Targets annotated with `noclippy` will not be formatted
+ if aspect_ctx and "noclippy" in aspect_ctx.rule.attr.tags:
+ return None
+
+ # Obviously ignore any targets that don't contain `CrateInfo`
+ if rust_common.crate_info not in target:
+ return None
+
+ return target[rust_common.crate_info]
+
+def _clippy_aspect_impl(target, ctx):
+ crate_info = _get_clippy_ready_crate_info(target, ctx)
+ if not crate_info:
+ return [ClippyInfo(output = depset([]))]
+
+ toolchain = find_toolchain(ctx)
+ cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+
+ dep_info, build_info, linkstamps = collect_deps(
+ deps = crate_info.deps,
+ proc_macro_deps = crate_info.proc_macro_deps,
+ aliases = crate_info.aliases,
+ # Clippy doesn't need to invoke transitive linking, therefore doesn't need linkstamps.
+ are_linkstamps_supported = False,
+ )
+
+ compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
+ ctx,
+ ctx.rule.file,
+ ctx.rule.files,
+ linkstamps,
+ toolchain,
+ cc_toolchain,
+ feature_configuration,
+ crate_info,
+ dep_info,
+ build_info,
+ )
+
+ args, env = construct_arguments(
+ ctx = ctx,
+ attr = ctx.rule.attr,
+ file = ctx.file,
+ toolchain = toolchain,
+ tool_path = toolchain.clippy_driver.path,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ crate_info = crate_info,
+ dep_info = dep_info,
+ linkstamp_outs = linkstamp_outs,
+ ambiguous_libs = ambiguous_libs,
+ output_hash = determine_output_hash(crate_info.root, ctx.label),
+ rust_flags = [],
+ out_dir = out_dir,
+ build_env_files = build_env_files,
+ build_flags_files = build_flags_files,
+ emit = ["dep-info", "metadata"],
+ )
+
+ if crate_info.is_test:
+ args.rustc_flags.add("--test")
+
+ # For remote execution purposes, the clippy_out file must be a sibling of crate_info.output
+ # or rustc may fail to create intermediate output files because the directory does not exist.
+ if ctx.attr._capture_output[CaptureClippyOutputInfo].capture_output:
+ clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.out", sibling = crate_info.output)
+ args.process_wrapper_flags.add("--stderr-file", clippy_out.path)
+
+ # If we are capturing the output, we want the build system to be able to keep going
+ # and consume the output. Some clippy lints are denials, so we treat them as warnings.
+ args.rustc_flags.add("-Wclippy::all")
+ else:
+ # A marker file indicating clippy has executed successfully.
+ # This file is necessary because "ctx.actions.run" mandates an output.
+ clippy_out = ctx.actions.declare_file(ctx.label.name + ".clippy.ok", sibling = crate_info.output)
+ args.process_wrapper_flags.add("--touch-file", clippy_out.path)
+
+ # Turn any warnings from clippy or rustc into an error, as otherwise
+ # Bazel will consider the execution result of the aspect to be "success",
+ # and Clippy won't be re-triggered unless the source file is modified.
+ if "__bindgen" in ctx.rule.attr.tags:
+ # bindgen-generated content is likely to trigger warnings, so
+ # only fail on clippy warnings
+ args.rustc_flags.add("-Dclippy::style")
+ args.rustc_flags.add("-Dclippy::correctness")
+ args.rustc_flags.add("-Dclippy::complexity")
+ args.rustc_flags.add("-Dclippy::perf")
+ else:
+ # fail on any warning
+ args.rustc_flags.add("-Dwarnings")
+
+ # Upstream clippy requires one of these two filenames or it silently uses
+ # the default config. Enforce the naming so users are not confused.
+ valid_config_file_names = [".clippy.toml", "clippy.toml"]
+ if ctx.file._config.basename not in valid_config_file_names:
+ fail("The clippy config file must be named one of: {}".format(valid_config_file_names))
+ env["CLIPPY_CONF_DIR"] = "${{pwd}}/{}".format(ctx.file._config.dirname)
+ compile_inputs = depset([ctx.file._config], transitive = [compile_inputs])
+
+ ctx.actions.run(
+ executable = ctx.executable._process_wrapper,
+ inputs = compile_inputs,
+ outputs = [clippy_out],
+ env = env,
+ tools = [toolchain.clippy_driver],
+ arguments = args.all,
+ mnemonic = "Clippy",
+ )
+
+ return [
+ OutputGroupInfo(clippy_checks = depset([clippy_out])),
+ ClippyInfo(output = depset([clippy_out])),
+ ]
+
+# Example: Run the clippy checker on all targets in the codebase.
+# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \
+# --output_groups=clippy_checks \
+# //...
+rust_clippy_aspect = aspect(
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ attrs = {
+ "_capture_output": attr.label(
+ doc = "Value of the `capture_clippy_output` build setting",
+ default = Label("//:capture_clippy_output"),
+ ),
+ "_cc_toolchain": attr.label(
+ doc = (
+ "Required attribute to access the cc_toolchain. See [Accessing the C++ toolchain]" +
+ "(https://docs.bazel.build/versions/master/integrating-with-rules-cc.html#accessing-the-c-toolchain)"
+ ),
+ default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+ ),
+ "_config": attr.label(
+ doc = "The `clippy.toml` file used for configuration",
+ allow_single_file = True,
+ default = Label("//:clippy.toml"),
+ ),
+ "_error_format": attr.label(
+ doc = "The desired `--error-format` flags for clippy",
+ default = "//:error_format",
+ ),
+ "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
+ "_process_wrapper": attr.label(
+ doc = "A process wrapper for running clippy on all platforms",
+ default = Label("//util/process_wrapper"),
+ executable = True,
+ cfg = "exec",
+ ),
+ },
+ provides = [ClippyInfo],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ implementation = _clippy_aspect_impl,
+ doc = """\
+Executes the clippy checker on specified targets.
+
+This aspect applies to existing rust_library, rust_test, and rust_binary rules.
+
+As an example, if the following is defined in `examples/hello_lib/BUILD.bazel`:
+
+```python
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+)
+
+rust_test(
+ name = "greeting_test",
+ srcs = ["tests/greeting.rs"],
+ deps = [":hello_lib"],
+)
+```
+
+Then the targets can be analyzed with clippy using the following command:
+
+```output
+$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_clippy_aspect \
+ --output_groups=clippy_checks //hello_lib:all
+```
+""",
+)
+
+def _rust_clippy_rule_impl(ctx):
+ clippy_ready_targets = [dep for dep in ctx.attr.deps if "clippy_checks" in dir(dep[OutputGroupInfo])]
+ files = depset([], transitive = [dep[OutputGroupInfo].clippy_checks for dep in clippy_ready_targets])
+ return [DefaultInfo(files = files)]
+
+rust_clippy = rule(
+ implementation = _rust_clippy_rule_impl,
+ attrs = {
+ "deps": attr.label_list(
+ doc = "Rust targets to run clippy on.",
+ providers = [rust_common.crate_info],
+ aspects = [rust_clippy_aspect],
+ ),
+ },
+ doc = """\
+Executes the clippy checker on a specific target.
+
+Similar to `rust_clippy_aspect`, but allows specifying a list of dependencies \
+within the build system.
+
+For example, given the following example targets:
+
+```python
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+)
+
+rust_test(
+ name = "greeting_test",
+ srcs = ["tests/greeting.rs"],
+ deps = [":hello_lib"],
+)
+```
+
+Rust clippy can be set as a build target with the following:
+
+```python
+load("@rules_rust//rust:defs.bzl", "rust_clippy")
+
+rust_clippy(
+ name = "hello_library_clippy",
+ testonly = True,
+ deps = [
+ ":hello_lib",
+ ":greeting_test",
+ ],
+)
+```
+""",
+)
+
+def _capture_clippy_output_impl(ctx):
+ """Implementation of the `capture_clippy_output` rule
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list containing the CaptureClippyOutputInfo provider
+ """
+ return [CaptureClippyOutputInfo(capture_output = ctx.build_setting_value)]
+
+capture_clippy_output = rule(
+ doc = "Control whether to print clippy output or store it to a file, using the configured error_format.",
+ implementation = _capture_clippy_output_impl,
+ build_setting = config.bool(flag = True),
+)
diff --git a/rust/private/common.bzl b/rust/private/common.bzl
new file mode 100644
index 0000000..78a3011
--- /dev/null
+++ b/rust/private/common.bzl
@@ -0,0 +1,60 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""A resilient API layer wrapping compilation and other logic for Rust rules.
+
+This module is meant to be used by custom rules that need to compile Rust code
+and cannot simply rely on writing a macro that wraps `rust_library`. This module
+provides the lower-level interface to Rust providers, actions, and functions.
+Do not load this file directly; instead, load the top-level `defs.bzl` file,
+which exports the `rust_common` struct.
+
+In the Bazel lingo, `rust_common` gives the access to the Rust Sandwich API.
+"""
+
+load(":providers.bzl", "CrateInfo", "DepInfo", "StdLibInfo")
+
+# These constants only represent the default value for attributes and macros
+# defiend in `rules_rust`. Like any attribute public attribute, they can be
+# overwritten by the user on the rules they're defiend on.
+#
+# Note: Code in `.github/workflows/crate_universe.yaml` looks for this line, if
+# you remove it or change its format, you will also need to update that code.
+DEFAULT_RUST_VERSION = "1.59.0"
+DEFAULT_RUST_EDITION = "2018"
+
+def _create_crate_info(**kwargs):
+ """A constructor for a `CrateInfo` provider
+
+ This function should be used in place of directly creating a `CrateInfo`
+ provider to improve API stability.
+
+ Args:
+ **kwargs: An inital set of keyword arguments.
+
+ Returns:
+ CrateInfo: A provider
+ """
+ if not "wrapped_crate_type" in kwargs:
+ kwargs.update({"wrapped_crate_type": None})
+ return CrateInfo(**kwargs)
+
+rust_common = struct(
+ create_crate_info = _create_crate_info,
+ crate_info = CrateInfo,
+ dep_info = DepInfo,
+ stdlib_info = StdLibInfo,
+ default_edition = DEFAULT_RUST_EDITION,
+ default_version = DEFAULT_RUST_VERSION,
+)
diff --git a/rust/private/dummy_cc_toolchain/BUILD.bazel b/rust/private/dummy_cc_toolchain/BUILD.bazel
new file mode 100644
index 0000000..004d233
--- /dev/null
+++ b/rust/private/dummy_cc_toolchain/BUILD.bazel
@@ -0,0 +1,13 @@
+load(":dummy_cc_toolchain.bzl", "dummy_cc_toolchain")
+
+dummy_cc_toolchain(name = "dummy_cc_wasm32")
+
+# When compiling Rust code for wasm32, we avoid linking to cpp code so we introduce a dummy cc
+# toolchain since we know we'll never look it up.
+# TODO(jedmonds@spotify.com): Need to support linking C code to rust code when compiling for wasm32.
+toolchain(
+ name = "dummy_cc_wasm32_toolchain",
+ target_compatible_with = ["//rust/platform/cpu:wasm32"],
+ toolchain = ":dummy_cc_wasm32",
+ toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
diff --git a/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl b/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl
new file mode 100644
index 0000000..8ca8c8f
--- /dev/null
+++ b/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl
@@ -0,0 +1,9 @@
+# buildifier: disable=module-docstring
+def _dummy_cc_toolchain_impl(_ctx):
+ # The `all_files` attribute is referenced by rustc_compile_action().
+ return [platform_common.ToolchainInfo(all_files = depset([]))]
+
+dummy_cc_toolchain = rule(
+ implementation = _dummy_cc_toolchain_impl,
+ attrs = {},
+)
diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl
new file mode 100644
index 0000000..b471ac0
--- /dev/null
+++ b/rust/private/providers.bzl
@@ -0,0 +1,109 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Module containing definitions of all Rust providers."""
+
+CrateInfo = provider(
+ doc = "A provider containing general Crate information.",
+ fields = {
+ "aliases": "Dict[Label, String]: Renamed and aliased crates",
+ "compile_data": "depset[File]: Compile data required by this crate.",
+ "deps": "depset[DepVariantInfo]: This crate's (rust or cc) dependencies' providers.",
+ "edition": "str: The edition of this crate.",
+ "is_test": "bool: If the crate is being compiled in a test context",
+ "name": "str: The name of this crate.",
+ "output": "File: The output File that will be produced, depends on crate type.",
+ "owner": "Label: The label of the target that produced this CrateInfo",
+ "proc_macro_deps": "depset[DepVariantInfo]: This crate's rust proc_macro dependencies' providers.",
+ "root": "File: The source File entrypoint to this crate, eg. lib.rs",
+ "rustc_env": "Dict[String, String]: Additional `\"key\": \"value\"` environment variables to set for rustc.",
+ "srcs": "depset[File]: All source Files that are part of the crate.",
+ "type": (
+ "str: The type of this crate " +
+ "(see [rustc --crate-type](https://doc.rust-lang.org/rustc/command-line-arguments.html#--crate-type-a-list-of-types-of-crates-for-the-compiler-to-emit))."
+ ),
+ "wrapped_crate_type": (
+ "str, optional: The original crate type for targets generated using a previously defined " +
+ "crate (typically tests using the `rust_test::crate` attribute)"
+ ),
+ },
+)
+
+DepInfo = provider(
+ doc = "A provider containing information about a Crate's dependencies.",
+ fields = {
+ "dep_env": "File: File with environment variables direct dependencies build scripts rely upon.",
+ "direct_crates": "depset[AliasableDepInfo]",
+ "link_search_path_files": "depset[File]: All transitive files containing search paths to pass to the linker",
+ "transitive_build_infos": "depset[BuildInfo]",
+ "transitive_crate_outputs": "depset[File]: All transitive crate outputs.",
+ "transitive_crates": "depset[CrateInfo]",
+ "transitive_noncrates": "depset[LinkerInput]: All transitive dependencies that aren't crates.",
+ },
+)
+
+BuildInfo = provider(
+ doc = "A provider containing `rustc` build settings for a given Crate.",
+ fields = {
+ "dep_env": "File: extra build script environment varibles to be set to direct dependencies.",
+ "flags": "File: file containing additional flags to pass to rustc",
+ "link_flags": "File: file containing flags to pass to the linker",
+ "link_search_paths": "File: file containing search paths to pass to the linker",
+ "out_dir": "File: directory containing the result of a build script",
+ "rustc_env": "File: file containing additional environment variables to set for rustc.",
+ },
+)
+
+DepVariantInfo = provider(
+ doc = "A wrapper provider for a dependency of a crate. The dependency can be a Rust " +
+ "dependency, in which case the `crate_info` and `dep_info` fields will be populated, " +
+ "a Rust build script dependency, in which case `build_info` will be populated, or a " +
+ "C/C++ dependency, in which case `cc_info` will be populated.",
+ fields = {
+ "build_info": "BuildInfo: The BuildInfo of a Rust dependency",
+ "cc_info": "CcInfo: The CcInfo of a C/C++ dependency",
+ "crate_info": "CrateInfo: The CrateInfo of a Rust dependency",
+ "dep_info": "DepInfo: The DepInfo of a Rust dependency",
+ },
+)
+
+StdLibInfo = provider(
+ doc = (
+ "A collection of files either found within the `rust-stdlib` artifact or " +
+ "generated based on existing files."
+ ),
+ fields = {
+ "alloc_files": "List[File]: `.a` files related to the `alloc` module.",
+ "between_alloc_and_core_files": "List[File]: `.a` files related to the `compiler_builtins` module.",
+ "between_core_and_std_files": "List[File]: `.a` files related to all modules except `adler`, `alloc`, `compiler_builtins`, `core`, and `std`.",
+ "core_files": "List[File]: `.a` files related to the `core` and `adler` modules",
+ "dot_a_files": "Depset[File]: Generated `.a` files",
+ "self_contained_files": "List[File]: All `.o` files from the `self-contained` directory.",
+ "srcs": "List[Target]: All targets from the original `srcs` attribute.",
+ "std_files": "Depset[File]: `.a` files associated with the `std` module.",
+ "std_rlibs": "List[File]: All `.rlib` files",
+ },
+)
+
+CaptureClippyOutputInfo = provider(
+ doc = "Value of the `capture_clippy_output` build setting",
+ fields = {"capture_output": "Value of the `capture_clippy_output` build setting"},
+)
+
+ClippyInfo = provider(
+ doc = "Provides information on a clippy run.",
+ fields = {
+ "output": "File with the clippy output.",
+ },
+)
diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
new file mode 100644
index 0000000..b6c83aa
--- /dev/null
+++ b/rust/private/repository_utils.bzl
@@ -0,0 +1,587 @@
+"""Utility macros for use in rules_rust repository rules"""
+
+load("//rust:known_shas.bzl", "FILE_KEY_TO_SHA")
+load(
+ "//rust/platform:triple_mappings.bzl",
+ "system_to_binary_ext",
+ "system_to_dylib_ext",
+ "system_to_staticlib_ext",
+ "system_to_stdlib_linkflags",
+ "triple_to_constraint_set",
+ "triple_to_system",
+)
+
+DEFAULT_TOOLCHAIN_NAME_PREFIX = "toolchain_for"
+DEFAULT_STATIC_RUST_URL_TEMPLATES = ["https://static.rust-lang.org/dist/{}.tar.gz"]
+
+_build_file_for_compiler_template = """\
+load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
+
+filegroup(
+ name = "rustc",
+ srcs = ["bin/rustc{binary_ext}"],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "rustc_lib",
+ srcs = glob(
+ [
+ "bin/*{dylib_ext}",
+ "lib/*{dylib_ext}",
+ "lib/rustlib/{target_triple}/codegen-backends/*{dylib_ext}",
+ "lib/rustlib/{target_triple}/bin/rust-lld{binary_ext}",
+ "lib/rustlib/{target_triple}/lib/*{dylib_ext}",
+ ],
+ allow_empty = True,
+ ),
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "rustdoc",
+ srcs = ["bin/rustdoc{binary_ext}"],
+ visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_compiler(target_triple):
+ """Emits a BUILD file the compiler `.tar.gz`.
+
+ Args:
+ target_triple (str): The triple of the target platform
+
+ Returns:
+ str: The contents of a BUILD file
+ """
+ system = triple_to_system(target_triple)
+ return _build_file_for_compiler_template.format(
+ binary_ext = system_to_binary_ext(system),
+ staticlib_ext = system_to_staticlib_ext(system),
+ dylib_ext = system_to_dylib_ext(system),
+ target_triple = target_triple,
+ )
+
+_build_file_for_cargo_template = """\
+load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
+
+filegroup(
+ name = "cargo",
+ srcs = ["bin/cargo{binary_ext}"],
+ visibility = ["//visibility:public"],
+)"""
+
+def BUILD_for_cargo(target_triple):
+ """Emits a BUILD file the cargo `.tar.gz`.
+
+ Args:
+ target_triple (str): The triple of the target platform
+
+ Returns:
+ str: The contents of a BUILD file
+ """
+ system = triple_to_system(target_triple)
+ return _build_file_for_cargo_template.format(
+ binary_ext = system_to_binary_ext(system),
+ )
+
+_build_file_for_rustfmt_template = """\
+load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
+
+filegroup(
+ name = "rustfmt_bin",
+ srcs = ["bin/rustfmt{binary_ext}"],
+ visibility = ["//visibility:public"],
+)
+
+sh_binary(
+ name = "rustfmt",
+ srcs = [":rustfmt_bin"],
+ visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_rustfmt(target_triple):
+ """Emits a BUILD file the rustfmt `.tar.gz`.
+
+ Args:
+ target_triple (str): The triple of the target platform
+
+ Returns:
+ str: The contents of a BUILD file
+ """
+ system = triple_to_system(target_triple)
+ return _build_file_for_rustfmt_template.format(
+ binary_ext = system_to_binary_ext(system),
+ )
+
+_build_file_for_clippy_template = """\
+load("@rules_rust//rust:toolchain.bzl", "rust_toolchain")
+
+filegroup(
+ name = "clippy_driver_bin",
+ srcs = ["bin/clippy-driver{binary_ext}"],
+ visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_clippy(target_triple):
+ """Emits a BUILD file the clippy `.tar.gz`.
+
+ Args:
+ target_triple (str): The triple of the target platform
+
+ Returns:
+ str: The contents of a BUILD file
+ """
+ system = triple_to_system(target_triple)
+ return _build_file_for_clippy_template.format(binary_ext = system_to_binary_ext(system))
+
+_build_file_for_stdlib_template = """\
+load("@rules_rust//rust:toolchain.bzl", "rust_stdlib_filegroup")
+
+rust_stdlib_filegroup(
+ name = "rust_std-{target_triple}",
+ srcs = glob(
+ [
+ "lib/rustlib/{target_triple}/lib/*.rlib",
+ "lib/rustlib/{target_triple}/lib/*{dylib_ext}",
+ "lib/rustlib/{target_triple}/lib/*{staticlib_ext}",
+ "lib/rustlib/{target_triple}/lib/self-contained/**",
+ ],
+ # Some patterns (e.g. `lib/*.a`) don't match anything, see https://github.com/bazelbuild/rules_rust/pull/245
+ allow_empty = True,
+ ),
+ visibility = ["//visibility:public"],
+)
+
+# For legacy support
+alias(
+ name = "rust_lib-{target_triple}",
+ actual = "rust_std-{target_triple}",
+ visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_stdlib(target_triple):
+ """Emits a BUILD file the stdlib `.tar.gz`.
+
+ Args:
+ target_triple (str): The triple of the target platform
+
+ Returns:
+ str: The contents of a BUILD file
+ """
+ system = triple_to_system(target_triple)
+ return _build_file_for_stdlib_template.format(
+ binary_ext = system_to_binary_ext(system),
+ staticlib_ext = system_to_staticlib_ext(system),
+ dylib_ext = system_to_dylib_ext(system),
+ target_triple = target_triple,
+ )
+
+_build_file_for_rust_toolchain_template = """\
+rust_toolchain(
+ name = "{toolchain_name}_impl",
+ rust_doc = "@{workspace_name}//:rustdoc",
+ rust_std = "@{workspace_name}//:rust_std-{target_triple}",
+ rustc = "@{workspace_name}//:rustc",
+ rustfmt = {rustfmt_label},
+ cargo = "@{workspace_name}//:cargo",
+ clippy_driver = "@{workspace_name}//:clippy_driver_bin",
+ rustc_lib = "@{workspace_name}//:rustc_lib",
+ rustc_srcs = {rustc_srcs},
+ binary_ext = "{binary_ext}",
+ staticlib_ext = "{staticlib_ext}",
+ dylib_ext = "{dylib_ext}",
+ stdlib_linkflags = [{stdlib_linkflags}],
+ os = "{system}",
+ default_edition = "{default_edition}",
+ exec_triple = "{exec_triple}",
+ target_triple = "{target_triple}",
+ visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_rust_toolchain(
+ workspace_name,
+ name,
+ exec_triple,
+ target_triple,
+ include_rustc_srcs,
+ default_edition,
+ include_rustfmt,
+ stdlib_linkflags = None):
+ """Emits a toolchain declaration to match an existing compiler and stdlib.
+
+ Args:
+ workspace_name (str): The name of the workspace that this toolchain resides in
+ name (str): The name of the toolchain declaration
+ exec_triple (str): The rust-style target that this compiler runs on
+ target_triple (str): The rust-style target triple of the tool
+ include_rustc_srcs (bool, optional): Whether to download rustc's src code. This is required in order to use rust-analyzer support. Defaults to False.
+ default_edition (str): Default Rust edition.
+ include_rustfmt (bool): Whether rustfmt is present in the toolchain.
+ stdlib_linkflags (list, optional): Overriden flags needed for linking to rust
+ stdlib, akin to BAZEL_LINKLIBS. Defaults to
+ None.
+
+
+ Returns:
+ str: A rendered template of a `rust_toolchain` declaration
+ """
+ system = triple_to_system(target_triple)
+ if stdlib_linkflags == None:
+ stdlib_linkflags = ", ".join(['"%s"' % x for x in system_to_stdlib_linkflags(system)])
+
+ rustc_srcs = "None"
+ if include_rustc_srcs:
+ rustc_srcs = "\"@{workspace_name}//lib/rustlib/src:rustc_srcs\"".format(workspace_name = workspace_name)
+ rustfmt_label = "None"
+ if include_rustfmt:
+ rustfmt_label = "\"@{workspace_name}//:rustfmt_bin\"".format(workspace_name = workspace_name)
+
+ return _build_file_for_rust_toolchain_template.format(
+ toolchain_name = name,
+ workspace_name = workspace_name,
+ binary_ext = system_to_binary_ext(system),
+ staticlib_ext = system_to_staticlib_ext(system),
+ dylib_ext = system_to_dylib_ext(system),
+ rustc_srcs = rustc_srcs,
+ stdlib_linkflags = stdlib_linkflags,
+ system = system,
+ default_edition = default_edition,
+ exec_triple = exec_triple,
+ target_triple = target_triple,
+ rustfmt_label = rustfmt_label,
+ )
+
+_build_file_for_toolchain_template = """\
+toolchain(
+ name = "{name}",
+ exec_compatible_with = {exec_constraint_sets_serialized},
+ target_compatible_with = {target_constraint_sets_serialized},
+ toolchain = "@{parent_workspace_name}//:{name}_impl",
+ toolchain_type = "@rules_rust//rust:toolchain",
+)
+"""
+
+def BUILD_for_toolchain(name, parent_workspace_name, exec_triple, target_triple):
+ return _build_file_for_toolchain_template.format(
+ name = name,
+ exec_constraint_sets_serialized = serialized_constraint_set_from_triple(exec_triple),
+ target_constraint_sets_serialized = serialized_constraint_set_from_triple(target_triple),
+ parent_workspace_name = parent_workspace_name,
+ )
+
+def load_rustfmt(ctx):
+ """Loads a rustfmt binary and yields corresponding BUILD for it
+
+ Args:
+ ctx (repository_ctx): The repository rule's context object
+
+ Returns:
+ str: The BUILD file contents for this rustfmt binary
+ """
+ target_triple = ctx.attr.exec_triple
+
+ load_arbitrary_tool(
+ ctx,
+ iso_date = ctx.attr.iso_date,
+ target_triple = target_triple,
+ tool_name = "rustfmt",
+ tool_subdirectories = ["rustfmt-preview"],
+ version = ctx.attr.rustfmt_version,
+ )
+
+ return BUILD_for_rustfmt(target_triple)
+
+def load_rust_compiler(ctx):
+ """Loads a rust compiler and yields corresponding BUILD for it
+
+ Args:
+ ctx (repository_ctx): A repository_ctx.
+
+ Returns:
+ str: The BUILD file contents for this compiler and compiler library
+ """
+
+ target_triple = ctx.attr.exec_triple
+ load_arbitrary_tool(
+ ctx,
+ iso_date = ctx.attr.iso_date,
+ target_triple = target_triple,
+ tool_name = "rust",
+ tool_subdirectories = ["rustc", "clippy-preview", "cargo"],
+ version = ctx.attr.version,
+ )
+
+ compiler_build_file = BUILD_for_compiler(target_triple) + BUILD_for_clippy(target_triple) + BUILD_for_cargo(target_triple)
+
+ return compiler_build_file
+
+def should_include_rustc_srcs(repository_ctx):
+ """Determing whether or not to include rustc sources in the toolchain.
+
+ Args:
+ repository_ctx (repository_ctx): The repository rule's context object
+
+ Returns:
+ bool: Whether or not to include rustc source files in a `rustc_toolchain`
+ """
+
+ # The environment variable will always take precedence over the attribute.
+ include_rustc_srcs_env = repository_ctx.os.environ.get("RULES_RUST_TOOLCHAIN_INCLUDE_RUSTC_SRCS")
+ if include_rustc_srcs_env != None:
+ return include_rustc_srcs_env.lower() in ["true", "1"]
+
+ return getattr(repository_ctx.attr, "include_rustc_srcs", False)
+
+def load_rust_src(ctx):
+ """Loads the rust source code. Used by the rust-analyzer rust-project.json generator.
+
+ Args:
+ ctx (ctx): A repository_ctx.
+ """
+ tool_suburl = produce_tool_suburl("rust-src", None, ctx.attr.version, ctx.attr.iso_date)
+ static_rust = ctx.os.environ.get("STATIC_RUST_URL", "https://static.rust-lang.org")
+ url = "{}/dist/{}.tar.gz".format(static_rust, tool_suburl)
+
+ tool_path = produce_tool_path("rust-src", None, ctx.attr.version)
+ archive_path = tool_path + ".tar.gz"
+ ctx.download(
+ url,
+ output = archive_path,
+ sha256 = ctx.attr.sha256s.get(tool_suburl) or FILE_KEY_TO_SHA.get(tool_suburl) or "",
+ auth = _make_auth_dict(ctx, [url]),
+ )
+ ctx.extract(
+ archive_path,
+ output = "lib/rustlib/src",
+ stripPrefix = "{}/rust-src/lib/rustlib/src/rust".format(tool_path),
+ )
+ ctx.file(
+ "lib/rustlib/src/BUILD.bazel",
+ """\
+filegroup(
+ name = "rustc_srcs",
+ srcs = glob(["**/*"]),
+ visibility = ["//visibility:public"],
+)""",
+ )
+
+def load_rust_stdlib(ctx, target_triple):
+ """Loads a rust standard library and yields corresponding BUILD for it
+
+ Args:
+ ctx (repository_ctx): A repository_ctx.
+ target_triple (str): The rust-style target triple of the tool
+
+ Returns:
+ str: The BUILD file contents for this stdlib, and a toolchain decl to match
+ """
+
+ load_arbitrary_tool(
+ ctx,
+ iso_date = ctx.attr.iso_date,
+ target_triple = target_triple,
+ tool_name = "rust-std",
+ tool_subdirectories = ["rust-std-{}".format(target_triple)],
+ version = ctx.attr.version,
+ )
+
+ toolchain_prefix = ctx.attr.toolchain_name_prefix or DEFAULT_TOOLCHAIN_NAME_PREFIX
+ stdlib_build_file = BUILD_for_stdlib(target_triple)
+
+ stdlib_linkflags = None
+ if "BAZEL_RUST_STDLIB_LINKFLAGS" in ctx.os.environ:
+ stdlib_linkflags = ctx.os.environ["BAZEL_RUST_STDLIB_LINKFLAGS"].split(":")
+
+ toolchain_build_file = BUILD_for_rust_toolchain(
+ name = "{toolchain_prefix}_{target_triple}".format(
+ toolchain_prefix = toolchain_prefix,
+ target_triple = target_triple,
+ ),
+ exec_triple = ctx.attr.exec_triple,
+ include_rustc_srcs = should_include_rustc_srcs(ctx),
+ target_triple = target_triple,
+ stdlib_linkflags = stdlib_linkflags,
+ workspace_name = ctx.attr.name,
+ default_edition = ctx.attr.edition,
+ include_rustfmt = not (not ctx.attr.rustfmt_version),
+ )
+
+ return stdlib_build_file + toolchain_build_file
+
+def load_rustc_dev_nightly(ctx, target_triple):
+ """Loads the nightly rustc dev component
+
+ Args:
+ ctx: A repository_ctx.
+ target_triple: The rust-style target triple of the tool
+ """
+
+ subdir_name = "rustc-dev"
+ if ctx.attr.iso_date < "2020-12-24":
+ subdir_name = "rustc-dev-{}".format(target_triple)
+
+ load_arbitrary_tool(
+ ctx,
+ iso_date = ctx.attr.iso_date,
+ target_triple = target_triple,
+ tool_name = "rustc-dev",
+ tool_subdirectories = [subdir_name],
+ version = ctx.attr.version,
+ )
+
+def load_llvm_tools(ctx, target_triple):
+ """Loads the llvm tools
+
+ Args:
+ ctx: A repository_ctx.
+ target_triple: The rust-style target triple of the tool
+ """
+ load_arbitrary_tool(
+ ctx,
+ iso_date = ctx.attr.iso_date,
+ target_triple = target_triple,
+ tool_name = "llvm-tools",
+ tool_subdirectories = ["llvm-tools-preview"],
+ version = ctx.attr.version,
+ )
+
+def check_version_valid(version, iso_date, param_prefix = ""):
+ """Verifies that the provided rust version and iso_date make sense.
+
+ Args:
+ version (str): The rustc version
+ iso_date (str): The rustc nightly version's iso date
+ param_prefix (str, optional): The name of the tool who's version is being checked.
+ """
+
+ if not version and iso_date:
+ fail("{param_prefix}iso_date must be paired with a {param_prefix}version".format(param_prefix = param_prefix))
+
+ if version in ("beta", "nightly") and not iso_date:
+ fail("{param_prefix}iso_date must be specified if version is 'beta' or 'nightly'".format(param_prefix = param_prefix))
+
+def serialized_constraint_set_from_triple(target_triple):
+ """Returns a string representing a set of constraints
+
+ Args:
+ target_triple (str): The target triple of the constraint set
+
+ Returns:
+ str: Formatted string representing the serialized constraint
+ """
+ constraint_set = triple_to_constraint_set(target_triple)
+ constraint_set_strs = []
+ for constraint in constraint_set:
+ constraint_set_strs.append("\"{}\"".format(constraint))
+ return "[{}]".format(", ".join(constraint_set_strs))
+
+def produce_tool_suburl(tool_name, target_triple, version, iso_date = None):
+ """Produces a fully qualified Rust tool name for URL
+
+ Args:
+ tool_name: The name of the tool per static.rust-lang.org
+ target_triple: The rust-style target triple of the tool
+ version: The version of the tool among "nightly", "beta', or an exact version.
+ iso_date: The date of the tool (or None, if the version is a specific version).
+
+ Returns:
+ str: The fully qualified url path for the specified tool.
+ """
+ path = produce_tool_path(tool_name, target_triple, version)
+ return iso_date + "/" + path if (iso_date and version in ("beta", "nightly")) else path
+
+def produce_tool_path(tool_name, target_triple, version):
+ """Produces a qualified Rust tool name
+
+ Args:
+ tool_name: The name of the tool per static.rust-lang.org
+ target_triple: The rust-style target triple of the tool
+ version: The version of the tool among "nightly", "beta', or an exact version.
+
+ Returns:
+ str: The qualified path for the specified tool.
+ """
+ if not tool_name:
+ fail("No tool name was provided")
+ if not version:
+ fail("No tool version was provided")
+ return "-".join([e for e in [tool_name, version, target_triple] if e])
+
+def load_arbitrary_tool(ctx, tool_name, tool_subdirectories, version, iso_date, target_triple, sha256 = ""):
+ """Loads a Rust tool, downloads, and extracts into the common workspace.
+
+ This function sources the tool from the Rust-lang static file server. The index is available at:
+ - https://static.rust-lang.org/dist/channel-rust-stable.toml
+ - https://static.rust-lang.org/dist/channel-rust-beta.toml
+ - https://static.rust-lang.org/dist/channel-rust-nightly.toml
+
+ The environment variable `STATIC_RUST_URL` can be used to replace the schema and hostname of
+ the URLs used for fetching assets. `https://static.rust-lang.org/dist/channel-rust-stable.toml`
+ becomes `${STATIC_RUST_URL}/dist/channel-rust-stable.toml`
+
+ Args:
+ ctx (repository_ctx): A repository_ctx (no attrs required).
+ tool_name (str): The name of the given tool per the archive naming.
+ tool_subdirectories (str): The subdirectories of the tool files (at a level below the root directory of
+ the archive). The root directory of the archive is expected to match
+ $TOOL_NAME-$VERSION-$TARGET_TRIPLE.
+ Example:
+ tool_name
+ | version
+ | | target_triple
+ v v v
+ rust-1.39.0-x86_64-unknown-linux-gnu/clippy-preview
+ .../rustc
+ .../etc
+ tool_subdirectories = ["clippy-preview", "rustc"]
+ version (str): The version of the tool among "nightly", "beta', or an exact version.
+ iso_date (str): The date of the tool (ignored if the version is a specific version).
+ target_triple (str): The rust-style target triple of the tool
+ sha256 (str, optional): The expected hash of hash of the Rust tool. Defaults to "".
+ """
+ check_version_valid(version, iso_date, param_prefix = tool_name + "_")
+
+ # View the indices mentioned in the docstring to find the tool_suburl for a given
+ # tool.
+ tool_suburl = produce_tool_suburl(tool_name, target_triple, version, iso_date)
+ urls = []
+
+ static_rust_url_from_env = ctx.os.environ.get("STATIC_RUST_URL")
+ if static_rust_url_from_env:
+ urls.append("{}/dist/{}.tar.gz".format(static_rust_url_from_env, tool_suburl))
+
+ for url in getattr(ctx.attr, "urls", DEFAULT_STATIC_RUST_URL_TEMPLATES):
+ new_url = url.format(tool_suburl)
+ if new_url not in urls:
+ urls.append(new_url)
+
+ tool_path = produce_tool_path(tool_name, target_triple, version)
+ archive_path = "{}.tar.gz".format(tool_path)
+ ctx.download(
+ urls,
+ output = archive_path,
+ sha256 = getattr(ctx.attr, "sha256s", dict()).get(tool_suburl) or
+ FILE_KEY_TO_SHA.get(tool_suburl) or
+ sha256,
+ auth = _make_auth_dict(ctx, urls),
+ )
+ for subdirectory in tool_subdirectories:
+ ctx.extract(
+ archive_path,
+ output = "",
+ stripPrefix = "{}/{}".format(tool_path, subdirectory),
+ )
+
+def _make_auth_dict(ctx, urls):
+ auth = getattr(ctx.attr, "auth", {})
+ if not auth:
+ return {}
+ ret = {}
+ for url in urls:
+ ret[url] = auth
+ return ret
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
new file mode 100644
index 0000000..58cc9c4
--- /dev/null
+++ b/rust/private/rust.bzl
@@ -0,0 +1,1151 @@
+# Copyright 2015 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# buildifier: disable=module-docstring
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:rustc.bzl", "rustc_compile_action")
+load(
+ "//rust/private:utils.bzl",
+ "compute_crate_name",
+ "dedent",
+ "determine_output_hash",
+ "expand_dict_value_locations",
+ "find_toolchain",
+ "get_import_macro_deps",
+ "transform_deps",
+)
+
+# TODO(marco): Separate each rule into its own file.
+
+def _assert_no_deprecated_attributes(_ctx):
+ """Forces a failure if any deprecated attributes were specified
+
+ Args:
+ _ctx (ctx): The current rule's context object
+ """
+ pass
+
+def _assert_correct_dep_mapping(ctx):
+ """Forces a failure if proc_macro_deps and deps are mixed inappropriately
+
+ Args:
+ ctx (ctx): The current rule's context object
+ """
+ for dep in ctx.attr.deps:
+ if rust_common.crate_info in dep:
+ if dep[rust_common.crate_info].type == "proc-macro":
+ fail(
+ "{} listed {} in its deps, but it is a proc-macro. It should instead be in the bazel property proc_macro_deps.".format(
+ ctx.label,
+ dep.label,
+ ),
+ )
+ for dep in ctx.attr.proc_macro_deps:
+ type = dep[rust_common.crate_info].type
+ if type != "proc-macro":
+ fail(
+ "{} listed {} in its proc_macro_deps, but it is not proc-macro, it is a {}. It should probably instead be listed in deps.".format(
+ ctx.label,
+ dep.label,
+ type,
+ ),
+ )
+
+def _determine_lib_name(name, crate_type, toolchain, lib_hash = None):
+ """See https://github.com/bazelbuild/rules_rust/issues/405
+
+ Args:
+ name (str): The name of the current target
+ crate_type (str): The `crate_type`
+ toolchain (rust_toolchain): The current `rust_toolchain`
+ lib_hash (str, optional): The hashed crate root path
+
+ Returns:
+ str: A unique library name
+ """
+ extension = None
+ prefix = ""
+ if crate_type in ("dylib", "cdylib", "proc-macro"):
+ extension = toolchain.dylib_ext
+ elif crate_type == "staticlib":
+ extension = toolchain.staticlib_ext
+ elif crate_type in ("lib", "rlib"):
+ # All platforms produce 'rlib' here
+ extension = ".rlib"
+ prefix = "lib"
+ elif crate_type == "bin":
+ fail("crate_type of 'bin' was detected in a rust_library. Please compile " +
+ "this crate as a rust_binary instead.")
+
+ if not extension:
+ fail(("Unknown crate_type: {}. If this is a cargo-supported crate type, " +
+ "please file an issue!").format(crate_type))
+
+ prefix = "lib"
+ if (toolchain.target_triple.find("windows") != -1) and crate_type not in ("lib", "rlib"):
+ prefix = ""
+ if toolchain.target_arch == "wasm32" and crate_type == "cdylib":
+ prefix = ""
+
+ return "{prefix}{name}{lib_hash}{extension}".format(
+ prefix = prefix,
+ name = name,
+ lib_hash = "-" + lib_hash if lib_hash else "",
+ extension = extension,
+ )
+
+def get_edition(attr, toolchain):
+ """Returns the Rust edition from either the current rule's attirbutes or the current `rust_toolchain`
+
+ Args:
+ attr (struct): The current rule's attributes
+ toolchain (rust_toolchain): The `rust_toolchain` for the current target
+
+ Returns:
+ str: The target Rust edition
+ """
+ if getattr(attr, "edition"):
+ return attr.edition
+ else:
+ return toolchain.default_edition
+
+def crate_root_src(attr, srcs, crate_type):
+ """Finds the source file for the crate root.
+
+ Args:
+ attr (struct): The attributes of the current target
+ srcs (list): A list of all sources for the target Crate.
+ crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
+
+ Returns:
+ File: The root File object for a given crate. See the following links for more details:
+ - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library
+ - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
+ """
+ default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
+
+ crate_root = None
+ if hasattr(attr, "crate_root"):
+ if attr.crate_root:
+ crate_root = attr.crate_root.files.to_list()[0]
+
+ if not crate_root:
+ crate_root = (
+ (srcs[0] if len(srcs) == 1 else None) or
+ _shortest_src_with_basename(srcs, default_crate_root_filename) or
+ _shortest_src_with_basename(srcs, attr.name + ".rs")
+ )
+ if not crate_root:
+ file_names = [default_crate_root_filename, attr.name + ".rs"]
+ fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
+ return crate_root
+
+def _shortest_src_with_basename(srcs, basename):
+ """Finds the shortest among the paths in srcs that match the desired basename.
+
+ Args:
+ srcs (list): A list of File objects
+ basename (str): The target basename to match against.
+
+ Returns:
+ File: The File object with the shortest path that matches `basename`
+ """
+ shortest = None
+ for f in srcs:
+ if f.basename == basename:
+ if not shortest or len(f.dirname) < len(shortest.dirname):
+ shortest = f
+ return shortest
+
+def _rust_library_impl(ctx):
+ """The implementation of the `rust_library` rule.
+
+ This rule provides CcInfo, so it can be used everywhere Bazel
+ expects rules_cc, but care must be taken to have the correct
+ dependencies on an allocator and std implemetation as needed.
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list of providers.
+ """
+ return _rust_library_common(ctx, "rlib")
+
+def _rust_static_library_impl(ctx):
+ """The implementation of the `rust_static_library` rule.
+
+ This rule provides CcInfo, so it can be used everywhere Bazel
+ expects rules_cc.
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list of providers.
+ """
+ return _rust_library_common(ctx, "staticlib")
+
+def _rust_shared_library_impl(ctx):
+ """The implementation of the `rust_shared_library` rule.
+
+ This rule provides CcInfo, so it can be used everywhere Bazel
+ expects rules_cc.
+
+ On Windows, a PDB file containing debugging information is available under
+ the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
+ is available under the key `dsym_folder` in `OutputGroupInfo`.
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list of providers.
+ """
+ return _rust_library_common(ctx, "cdylib")
+
+def _rust_proc_macro_impl(ctx):
+ """The implementation of the `rust_proc_macro` rule.
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list of providers.
+ """
+ return _rust_library_common(ctx, "proc-macro")
+
+def _rust_library_common(ctx, crate_type):
+ """The common implementation of the library-like rules.
+
+ Args:
+ ctx (ctx): The rule's context object
+ crate_type (String): one of lib|rlib|dylib|staticlib|cdylib|proc-macro
+
+ Returns:
+ list: A list of providers. See `rustc_compile_action`
+ """
+
+ # Find lib.rs
+ crate_root = crate_root_src(ctx.attr, ctx.files.srcs, "lib")
+ _assert_no_deprecated_attributes(ctx)
+ _assert_correct_dep_mapping(ctx)
+
+ toolchain = find_toolchain(ctx)
+
+ # Determine unique hash for this rlib.
+ # Note that we don't include a hash for `cdylib` since they are meant to be consumed externally and having a
+ # deterministic name is important since it ends up embedded in the executable. This is problematic when one needs
+ # to include the library with a specific filename into a larger application.
+ # (see https://github.com/bazelbuild/rules_rust/issues/405#issuecomment-993089889 for more details)
+ if crate_type != "cdylib":
+ output_hash = determine_output_hash(crate_root, ctx.label)
+ else:
+ output_hash = None
+
+ crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
+ rust_lib_name = _determine_lib_name(
+ crate_name,
+ crate_type,
+ toolchain,
+ output_hash,
+ )
+ rust_lib = ctx.actions.declare_file(rust_lib_name)
+
+ deps = transform_deps(ctx.attr.deps)
+ proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
+
+ return rustc_compile_action(
+ ctx = ctx,
+ attr = ctx.attr,
+ toolchain = toolchain,
+ crate_info = rust_common.create_crate_info(
+ name = crate_name,
+ type = crate_type,
+ root = crate_root,
+ srcs = depset(ctx.files.srcs),
+ deps = depset(deps),
+ proc_macro_deps = depset(proc_macro_deps),
+ aliases = ctx.attr.aliases,
+ output = rust_lib,
+ edition = get_edition(ctx.attr, toolchain),
+ rustc_env = ctx.attr.rustc_env,
+ is_test = False,
+ compile_data = depset(ctx.files.compile_data),
+ owner = ctx.label,
+ ),
+ output_hash = output_hash,
+ )
+
+def _rust_binary_impl(ctx):
+ """The implementation of the `rust_binary` rule
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list of providers. See `rustc_compile_action`
+ """
+ toolchain = find_toolchain(ctx)
+ crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
+ _assert_correct_dep_mapping(ctx)
+
+ output = ctx.actions.declare_file(ctx.label.name + toolchain.binary_ext)
+
+ deps = transform_deps(ctx.attr.deps)
+ proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
+
+ return rustc_compile_action(
+ ctx = ctx,
+ attr = ctx.attr,
+ toolchain = toolchain,
+ crate_info = rust_common.create_crate_info(
+ name = crate_name,
+ type = ctx.attr.crate_type,
+ root = crate_root_src(ctx.attr, ctx.files.srcs, ctx.attr.crate_type),
+ srcs = depset(ctx.files.srcs),
+ deps = depset(deps),
+ proc_macro_deps = depset(proc_macro_deps),
+ aliases = ctx.attr.aliases,
+ output = output,
+ edition = get_edition(ctx.attr, toolchain),
+ rustc_env = ctx.attr.rustc_env,
+ is_test = False,
+ compile_data = depset(ctx.files.compile_data),
+ owner = ctx.label,
+ ),
+ )
+
+def _rust_test_common(ctx, toolchain, output):
+ """Builds a Rust test binary.
+
+ Args:
+ ctx (ctx): The ctx object for the current target.
+ toolchain (rust_toolchain): The current `rust_toolchain`
+ output (File): The output File that will be produced, depends on crate type.
+
+ Returns:
+ list: The list of providers. See `rustc_compile_action`
+ """
+ _assert_no_deprecated_attributes(ctx)
+ _assert_correct_dep_mapping(ctx)
+
+ crate_name = compute_crate_name(ctx.workspace_name, ctx.label, toolchain, ctx.attr.crate_name)
+ crate_type = "bin"
+
+ deps = transform_deps(ctx.attr.deps)
+ proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
+
+ if ctx.attr.crate:
+ # Target is building the crate in `test` config
+ crate = ctx.attr.crate[rust_common.crate_info]
+
+ # Optionally join compile data
+ if crate.compile_data:
+ compile_data = depset(ctx.files.compile_data, transitive = [crate.compile_data])
+ else:
+ compile_data = depset(ctx.files.compile_data)
+
+ # Build the test binary using the dependency's srcs.
+ crate_info = rust_common.create_crate_info(
+ name = crate_name,
+ type = crate_type,
+ root = crate.root,
+ srcs = depset(ctx.files.srcs, transitive = [crate.srcs]),
+ deps = depset(deps, transitive = [crate.deps]),
+ proc_macro_deps = depset(proc_macro_deps, transitive = [crate.proc_macro_deps]),
+ aliases = ctx.attr.aliases,
+ output = output,
+ edition = crate.edition,
+ rustc_env = ctx.attr.rustc_env,
+ is_test = True,
+ compile_data = compile_data,
+ wrapped_crate_type = crate.type,
+ owner = ctx.label,
+ )
+ else:
+ # Target is a standalone crate. Build the test binary as its own crate.
+ crate_info = rust_common.create_crate_info(
+ name = crate_name,
+ type = crate_type,
+ root = crate_root_src(ctx.attr, ctx.files.srcs, "lib"),
+ srcs = depset(ctx.files.srcs),
+ deps = depset(deps),
+ proc_macro_deps = depset(proc_macro_deps),
+ aliases = ctx.attr.aliases,
+ output = output,
+ edition = get_edition(ctx.attr, toolchain),
+ rustc_env = ctx.attr.rustc_env,
+ is_test = True,
+ compile_data = depset(ctx.files.compile_data),
+ owner = ctx.label,
+ )
+
+ providers = rustc_compile_action(
+ ctx = ctx,
+ attr = ctx.attr,
+ toolchain = toolchain,
+ crate_info = crate_info,
+ rust_flags = ["--test"] if ctx.attr.use_libtest_harness else ["--cfg", "test"],
+ )
+ data = getattr(ctx.attr, "data", [])
+
+ env = expand_dict_value_locations(
+ ctx,
+ getattr(ctx.attr, "env", {}),
+ data,
+ )
+ providers.append(testing.TestEnvironment(env))
+
+ return providers
+
+def _rust_test_impl(ctx):
+ """The implementation of the `rust_test` rule
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list of providers. See `_rust_test_common`
+ """
+ toolchain = find_toolchain(ctx)
+
+ output = ctx.actions.declare_file(
+ ctx.label.name + toolchain.binary_ext,
+ )
+
+ return _rust_test_common(ctx, toolchain, output)
+
+_common_attrs = {
+ "aliases": attr.label_keyed_string_dict(
+ doc = dedent("""\
+ Remap crates to a new name or moniker for linkage to this target
+
+ These are other `rust_library` targets and will be presented as the new name given.
+ """),
+ ),
+ "compile_data": attr.label_list(
+ doc = dedent("""\
+ List of files used by this rule at compile time.
+
+ This attribute can be used to specify any data files that are embedded into
+ the library, such as via the
+ [`include_str!`](https://doc.rust-lang.org/std/macro.include_str!.html)
+ macro.
+ """),
+ allow_files = True,
+ ),
+ "crate_features": attr.string_list(
+ doc = dedent("""\
+ List of features to enable for this crate.
+
+ Features are defined in the code using the `#[cfg(feature = "foo")]`
+ configuration option. The features listed here will be passed to `rustc`
+ with `--cfg feature="${feature_name}"` flags.
+ """),
+ ),
+ "crate_name": attr.string(
+ doc = dedent("""\
+ Crate name to use for this target.
+
+ This must be a valid Rust identifier, i.e. it may contain only alphanumeric characters and underscores.
+ Defaults to the target name, with any hyphens replaced by underscores.
+ """),
+ ),
+ "crate_root": attr.label(
+ doc = dedent("""\
+ The file that will be passed to `rustc` to be used for building this crate.
+
+ If `crate_root` is not set, then this rule will look for a `lib.rs` file (or `main.rs` for rust_binary)
+ or the single file in `srcs` if `srcs` contains only one file.
+ """),
+ allow_single_file = [".rs"],
+ ),
+ "data": attr.label_list(
+ doc = dedent("""\
+ List of files used by this rule at compile time and runtime.
+
+ If including data at compile time with include_str!() and similar,
+ prefer `compile_data` over `data`, to prevent the data also being included
+ in the runfiles.
+ """),
+ allow_files = True,
+ ),
+ "deps": attr.label_list(
+ doc = dedent("""\
+ List of other libraries to be linked to this library target.
+
+ These can be either other `rust_library` targets or `cc_library` targets if
+ linking a native library.
+ """),
+ ),
+ "edition": attr.string(
+ doc = "The rust edition to use for this crate. Defaults to the edition specified in the rust_toolchain.",
+ ),
+ # Previously `proc_macro_deps` were a part of `deps`, and then proc_macro_host_transition was
+ # used into cfg="host" using `@local_config_platform//:host`.
+ # This fails for remote execution, which needs cfg="exec", and there isn't anything like
+ # `@local_config_platform//:exec` exposed.
+ "proc_macro_deps": attr.label_list(
+ doc = dedent("""\
+ List of `rust_library` targets with kind `proc-macro` used to help build this library target.
+ """),
+ cfg = "exec",
+ providers = [rust_common.crate_info],
+ ),
+ "rustc_env": attr.string_dict(
+ doc = dedent("""\
+ Dictionary of additional `"key": "value"` environment variables to set for rustc.
+
+ rust_test()/rust_binary() rules can use $(rootpath //package:target) to pass in the
+ location of a generated file or external tool. Cargo build scripts that wish to
+ expand locations should use cargo_build_script()'s build_script_env argument instead,
+ as build scripts are run in a different environment - see cargo_build_script()'s
+ documentation for more.
+ """),
+ ),
+ "rustc_env_files": attr.label_list(
+ doc = dedent("""\
+ Files containing additional environment variables to set for rustc.
+
+ These files should contain a single variable per line, of format
+ `NAME=value`, and newlines may be included in a value by ending a
+ line with a trailing back-slash (`\\\\`).
+
+ The order that these files will be processed is unspecified, so
+ multiple definitions of a particular variable are discouraged.
+
+ Note that the variables here are subject to
+ [workspace status](https://docs.bazel.build/versions/main/user-manual.html#workspace_status)
+ stamping should the `stamp` attribute be enabled. Stamp variables
+ should be wrapped in brackets in order to be resolved. E.g.
+ `NAME={WORKSPACE_STATUS_VARIABLE}`.
+ """),
+ allow_files = True,
+ ),
+ "rustc_flags": attr.string_list(
+ doc = dedent("""\
+ List of compiler flags passed to `rustc`.
+
+ These strings are subject to Make variable expansion for predefined
+ source/output path variables like `$location`, `$execpath`, and
+ `$rootpath`. This expansion is useful if you wish to pass a generated
+ file of arguments to rustc: `@$(location //package:target)`.
+ """),
+ ),
+ # TODO(stardoc): How do we provide additional documentation to an inherited attribute?
+ # "name": attr.string(
+ # doc = "This name will also be used as the name of the crate built by this rule.",
+ # `),
+ "srcs": attr.label_list(
+ doc = dedent("""\
+ List of Rust `.rs` source files used to build the library.
+
+ If `srcs` contains more than one file, then there must be a file either
+ named `lib.rs`. Otherwise, `crate_root` must be set to the source file that
+ is the root of the crate to be passed to rustc to build this crate.
+ """),
+ allow_files = [".rs"],
+ ),
+ "stamp": attr.int(
+ doc = dedent("""\
+ Whether to encode build information into the `Rustc` action. Possible values:
+
+ - `stamp = 1`: Always stamp the build information into the `Rustc` action, even in \
+ [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \
+ This setting should be avoided, since it potentially kills remote caching for the target and \
+ any downstream actions that depend on it.
+
+ - `stamp = 0`: Always replace build information by constant values. This gives good build result caching.
+
+ - `stamp = -1`: Embedding of build information is controlled by the \
+ [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
+
+ Stamped targets are not rebuilt unless their dependencies change.
+
+ For example if a `rust_library` is stamped, and a `rust_binary` depends on that library, the stamped
+ library won't be rebuilt when we change sources of the `rust_binary`. This is different from how
+ [`cc_library.linkstamps`](https://docs.bazel.build/versions/main/be/c-cpp.html#cc_library.linkstamp)
+ behaves.
+ """),
+ default = -1,
+ values = [1, 0, -1],
+ ),
+ "version": attr.string(
+ doc = "A version to inject in the cargo environment variable.",
+ default = "0.0.0",
+ ),
+ "_cc_toolchain": attr.label(
+ default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+ ),
+ "_error_format": attr.label(default = "//:error_format"),
+ "_extra_exec_rustc_flags": attr.label(default = "//:extra_exec_rustc_flags"),
+ "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
+ "_import_macro_dep": attr.label(
+ default = "@rules_rust//util/import",
+ ),
+ "_process_wrapper": attr.label(
+ default = Label("//util/process_wrapper"),
+ executable = True,
+ allow_single_file = True,
+ cfg = "exec",
+ ),
+ "_stamp_flag": attr.label(
+ doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
+ default = Label("//rust/private:stamp"),
+ ),
+}
+
+_rust_test_attrs = {
+ "crate": attr.label(
+ mandatory = False,
+ doc = dedent("""\
+ Target inline tests declared in the given crate
+
+ These tests are typically those that would be held out under
+ `#[cfg(test)]` declarations.
+ """),
+ ),
+ "env": attr.string_dict(
+ mandatory = False,
+ doc = dedent("""\
+ Specifies additional environment variables to set when the test is executed by bazel test.
+ Values are subject to `$(rootpath)`, `$(execpath)`, location, and
+ ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution.
+
+ Execpath returns absolute path, and in order to be able to construct the absolute path we
+ need to wrap the test binary in a launcher. Using a launcher comes with complications, such as
+ more complicated debugger attachment.
+ """),
+ ),
+ "use_libtest_harness": attr.bool(
+ mandatory = False,
+ default = True,
+ doc = dedent("""\
+ Whether to use `libtest`. For targets using this flag, individual tests can be run by using the
+ [--test_arg](https://docs.bazel.build/versions/4.0.0/command-line-reference.html#flag--test_arg) flag.
+ E.g. `bazel test //src:rust_test --test_arg=foo::test::test_fn`.
+ """),
+ ),
+ "_grep_includes": attr.label(
+ allow_single_file = True,
+ cfg = "exec",
+ default = Label("@bazel_tools//tools/cpp:grep-includes"),
+ executable = True,
+ ),
+}
+
+_common_providers = [
+ rust_common.crate_info,
+ rust_common.dep_info,
+ DefaultInfo,
+]
+
+rust_library = rule(
+ implementation = _rust_library_impl,
+ provides = _common_providers,
+ attrs = dict(_common_attrs.items()),
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Builds a Rust library crate.
+
+ Example:
+
+ Suppose you have the following directory structure for a simple Rust library crate:
+
+ ```output
+ [workspace]/
+ WORKSPACE
+ hello_lib/
+ BUILD
+ src/
+ greeter.rs
+ lib.rs
+ ```
+
+ `hello_lib/src/greeter.rs`:
+ ```rust
+ pub struct Greeter {
+ greeting: String,
+ }
+
+ impl Greeter {
+ pub fn new(greeting: &str) -> Greeter {
+ Greeter { greeting: greeting.to_string(), }
+ }
+
+ pub fn greet(&self, thing: &str) {
+ println!("{} {}", &self.greeting, thing);
+ }
+ }
+ ```
+
+ `hello_lib/src/lib.rs`:
+
+ ```rust
+ pub mod greeter;
+ ```
+
+ `hello_lib/BUILD`:
+ ```python
+ package(default_visibility = ["//visibility:public"])
+
+ load("@rules_rust//rust:defs.bzl", "rust_library")
+
+ rust_library(
+ name = "hello_lib",
+ srcs = [
+ "src/greeter.rs",
+ "src/lib.rs",
+ ],
+ )
+ ```
+
+ Build the library:
+ ```output
+ $ bazel build //hello_lib
+ INFO: Found 1 target...
+ Target //examples/rust/hello_lib:hello_lib up-to-date:
+ bazel-bin/examples/rust/hello_lib/libhello_lib.rlib
+ INFO: Elapsed time: 1.245s, Critical Path: 1.01s
+ ```
+ """),
+)
+
+rust_static_library = rule(
+ implementation = _rust_static_library_impl,
+ provides = _common_providers,
+ attrs = dict(_common_attrs.items()),
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Builds a Rust static library.
+
+ This static library will contain all transitively reachable crates and native objects.
+ It is meant to be used when producing an artifact that is then consumed by some other build system
+ (for example to produce an archive that Python program links against).
+
+ This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
+
+ When building the whole binary in Bazel, use `rust_library` instead.
+ """),
+)
+
+rust_shared_library = rule(
+ implementation = _rust_shared_library_impl,
+ provides = _common_providers,
+ attrs = dict(_common_attrs.items()),
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Builds a Rust shared library.
+
+ This shared library will contain all transitively reachable crates and native objects.
+ It is meant to be used when producing an artifact that is then consumed by some other build system
+ (for example to produce a shared library that Python program links against).
+
+ This rule provides CcInfo, so it can be used everywhere Bazel expects `rules_cc`.
+
+ When building the whole binary in Bazel, use `rust_library` instead.
+ """),
+)
+
+rust_proc_macro = rule(
+ implementation = _rust_proc_macro_impl,
+ provides = _common_providers,
+ attrs = dict(_common_attrs.items()),
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Builds a Rust proc-macro crate.
+ """),
+)
+
+_rust_binary_attrs = {
+ "crate_type": attr.string(
+ doc = dedent("""\
+ Crate type that will be passed to `rustc` to be used for building this crate.
+
+ This option is a temporary workaround and should be used only when building
+ for WebAssembly targets (//rust/platform:wasi and //rust/platform:wasm).
+ """),
+ default = "bin",
+ ),
+ "linker_script": attr.label(
+ doc = dedent("""\
+ Link script to forward into linker via rustc options.
+ """),
+ cfg = "exec",
+ allow_single_file = True,
+ ),
+ "out_binary": attr.bool(
+ doc = (
+ "Force a target, regardless of it's `crate_type`, to always mark the " +
+ "file as executable. This attribute is only used to support wasm targets but is " +
+ "expected to be removed following a resolution to https://github.com/bazelbuild/rules_rust/issues/771."
+ ),
+ default = False,
+ ),
+ "_grep_includes": attr.label(
+ allow_single_file = True,
+ cfg = "exec",
+ default = Label("@bazel_tools//tools/cpp:grep-includes"),
+ executable = True,
+ ),
+}
+
+rust_binary = rule(
+ implementation = _rust_binary_impl,
+ provides = _common_providers,
+ attrs = dict(_common_attrs.items() + _rust_binary_attrs.items()),
+ executable = True,
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Builds a Rust binary crate.
+
+ Example:
+
+ Suppose you have the following directory structure for a Rust project with a
+ library crate, `hello_lib`, and a binary crate, `hello_world` that uses the
+ `hello_lib` library:
+
+ ```output
+ [workspace]/
+ WORKSPACE
+ hello_lib/
+ BUILD
+ src/
+ lib.rs
+ hello_world/
+ BUILD
+ src/
+ main.rs
+ ```
+
+ `hello_lib/src/lib.rs`:
+ ```rust
+ pub struct Greeter {
+ greeting: String,
+ }
+
+ impl Greeter {
+ pub fn new(greeting: &str) -> Greeter {
+ Greeter { greeting: greeting.to_string(), }
+ }
+
+ pub fn greet(&self, thing: &str) {
+ println!("{} {}", &self.greeting, thing);
+ }
+ }
+ ```
+
+ `hello_lib/BUILD`:
+ ```python
+ package(default_visibility = ["//visibility:public"])
+
+ load("@rules_rust//rust:defs.bzl", "rust_library")
+
+ rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+ )
+ ```
+
+ `hello_world/src/main.rs`:
+ ```rust
+ extern crate hello_lib;
+
+ fn main() {
+ let hello = hello_lib::Greeter::new("Hello");
+ hello.greet("world");
+ }
+ ```
+
+ `hello_world/BUILD`:
+ ```python
+ load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+ rust_binary(
+ name = "hello_world",
+ srcs = ["src/main.rs"],
+ deps = ["//hello_lib"],
+ )
+ ```
+
+ Build and run `hello_world`:
+ ```
+ $ bazel run //hello_world
+ INFO: Found 1 target...
+ Target //examples/rust/hello_world:hello_world up-to-date:
+ bazel-bin/examples/rust/hello_world/hello_world
+ INFO: Elapsed time: 1.308s, Critical Path: 1.22s
+
+ INFO: Running command line: bazel-bin/examples/rust/hello_world/hello_world
+ Hello world
+ ```
+
+ On Windows, a PDB file containing debugging information is available under
+ the key `pdb_file` in `OutputGroupInfo`. Similarly on macOS, a dSYM folder
+ is available under the key `dsym_folder` in `OutputGroupInfo`.
+"""),
+)
+
+rust_test = rule(
+ implementation = _rust_test_impl,
+ provides = _common_providers,
+ attrs = dict(_common_attrs.items() +
+ _rust_test_attrs.items()),
+ executable = True,
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ test = True,
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Builds a Rust test crate.
+
+ Examples:
+
+ Suppose you have the following directory structure for a Rust library crate \
+ with unit test code in the library sources:
+
+ ```output
+ [workspace]/
+ WORKSPACE
+ hello_lib/
+ BUILD
+ src/
+ lib.rs
+ ```
+
+ `hello_lib/src/lib.rs`:
+ ```rust
+ pub struct Greeter {
+ greeting: String,
+ }
+
+ impl Greeter {
+ pub fn new(greeting: &str) -> Greeter {
+ Greeter { greeting: greeting.to_string(), }
+ }
+
+ pub fn greet(&self, thing: &str) -> String {
+ format!("{} {}", &self.greeting, thing)
+ }
+ }
+
+ #[cfg(test)]
+ mod test {
+ use super::Greeter;
+
+ #[test]
+ fn test_greeting() {
+ let hello = Greeter::new("Hi");
+ assert_eq!("Hi Rust", hello.greet("Rust"));
+ }
+ }
+ ```
+
+ To build and run the tests, simply add a `rust_test` rule with no `srcs` and \
+ only depends on the `hello_lib` `rust_library` target:
+
+ `hello_lib/BUILD`:
+ ```python
+ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+ rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+ )
+
+ rust_test(
+ name = "hello_lib_test",
+ deps = [":hello_lib"],
+ )
+ ```
+
+ Run the test with `bazel build //hello_lib:hello_lib_test`.
+
+ To run a crate or lib with the `#[cfg(test)]` configuration, handling inline \
+ tests, you should specify the crate directly like so.
+
+ ```python
+ rust_test(
+ name = "hello_lib_test",
+ crate = ":hello_lib",
+ # You may add other deps that are specific to the test configuration
+ deps = ["//some/dev/dep"],
+ )
+ ```
+
+ ### Example: `test` directory
+
+ Integration tests that live in the [`tests` directory][int-tests], they are \
+ essentially built as separate crates. Suppose you have the following directory \
+ structure where `greeting.rs` is an integration test for the `hello_lib` \
+ library crate:
+
+ [int-tests]: http://doc.rust-lang.org/book/testing.html#the-tests-directory
+
+ ```output
+ [workspace]/
+ WORKSPACE
+ hello_lib/
+ BUILD
+ src/
+ lib.rs
+ tests/
+ greeting.rs
+ ```
+
+ `hello_lib/tests/greeting.rs`:
+ ```rust
+ extern crate hello_lib;
+
+ use hello_lib;
+
+ #[test]
+ fn test_greeting() {
+ let hello = greeter::Greeter::new("Hello");
+ assert_eq!("Hello world", hello.greeting("world"));
+ }
+ ```
+
+ To build the `greeting.rs` integration test, simply add a `rust_test` target
+ with `greeting.rs` in `srcs` and a dependency on the `hello_lib` target:
+
+ `hello_lib/BUILD`:
+ ```python
+ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+ rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+ )
+
+ rust_test(
+ name = "greeting_test",
+ srcs = ["tests/greeting.rs"],
+ deps = [":hello_lib"],
+ )
+ ```
+
+ Run the test with `bazel build //hello_lib:hello_lib_test`.
+"""),
+)
+
+def rust_test_suite(name, srcs, **kwargs):
+ """A rule for creating a test suite for a set of `rust_test` targets.
+
+ This rule can be used for setting up typical rust [integration tests][it]. Given the following
+ directory structure:
+
+ ```text
+ [crate]/
+ BUILD.bazel
+ src/
+ lib.rs
+ main.rs
+ tests/
+ integrated_test_a.rs
+ integrated_test_b.rs
+ integrated_test_c.rs
+ patterns/
+ fibonacci_test.rs
+ ```
+
+ The rule can be used to generate [rust_test](#rust_test) targets for each source file under `tests`
+ and a [test_suite][ts] which encapsulates all tests.
+
+ ```python
+ load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test_suite")
+
+ rust_library(
+ name = "math_lib",
+ srcs = ["src/lib.rs"],
+ )
+
+ rust_binary(
+ name = "math_bin",
+ srcs = ["src/main.rs"],
+ )
+
+ rust_test_suite(
+ name = "integrated_tests_suite",
+ srcs = glob(["tests/**"]),
+ deps = [":math_lib"],
+ )
+ ```
+
+ [it]: https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html
+ [ts]: https://docs.bazel.build/versions/master/be/general.html#test_suite
+
+ Args:
+ name (str): The name of the `test_suite`.
+ srcs (list): All test sources, typically `glob(["tests/**/*.rs"])`.
+ **kwargs (dict): Additional keyword arguments for the underyling [rust_test](#rust_test) targets. The
+ `tags` argument is also passed to the generated `test_suite` target.
+ """
+ tests = []
+
+ for src in srcs:
+ if not src.endswith(".rs"):
+ fail("srcs should have `.rs` extensions")
+
+ # Prefixed with `name` to allow parameterization with macros
+ # The test name should not end with `.rs`
+ test_name = name + "_" + src[:-3]
+ rust_test(
+ name = test_name,
+ crate_name = test_name.replace("/", "_"),
+ srcs = [src],
+ **kwargs
+ )
+ tests.append(test_name)
+
+ native.test_suite(
+ name = name,
+ tests = tests,
+ tags = kwargs.get("tags", None),
+ )
diff --git a/rust/private/rust_analyzer.bzl b/rust/private/rust_analyzer.bzl
new file mode 100644
index 0000000..e28ca30
--- /dev/null
+++ b/rust/private/rust_analyzer.bzl
@@ -0,0 +1,240 @@
+# Copyright 2020 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Rust Analyzer Bazel rules.
+
+rust_analyzer will generate a rust-project.json file for the
+given targets. This file can be consumed by rust-analyzer as an alternative
+to Cargo.toml files.
+"""
+
+load("//rust/platform:triple_mappings.bzl", "system_to_dylib_ext", "triple_to_system")
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:rustc.bzl", "BuildInfo")
+load("//rust/private:utils.bzl", "dedent", "find_toolchain")
+
+RustAnalyzerInfo = provider(
+ doc = "RustAnalyzerInfo holds rust crate metadata for targets",
+ fields = {
+ "build_info": "BuildInfo: build info for this crate if present",
+ "cfgs": "List[String]: features or other compilation --cfg settings",
+ "crate": "rust_common.crate_info",
+ "crate_specs": "Depset[File]: transitive closure of OutputGroupInfo files",
+ "deps": "List[RustAnalyzerInfo]: direct dependencies",
+ "env": "Dict{String: String}: Environment variables, used for the `env!` macro",
+ "proc_macro_dylib_path": "File: compiled shared library output of proc-macro rule",
+ },
+)
+
+def _rust_analyzer_aspect_impl(target, ctx):
+ if rust_common.crate_info not in target:
+ return []
+
+ toolchain = find_toolchain(ctx)
+
+ # Always add `test` & `debug_assertions`. See rust-analyzer source code:
+ # https://github.com/rust-analyzer/rust-analyzer/blob/2021-11-15/crates/project_model/src/workspace.rs#L529-L531
+ cfgs = ["test", "debug_assertions"]
+ if hasattr(ctx.rule.attr, "crate_features"):
+ cfgs += ['feature="{}"'.format(f) for f in ctx.rule.attr.crate_features]
+ if hasattr(ctx.rule.attr, "rustc_flags"):
+ cfgs += [f[6:] for f in ctx.rule.attr.rustc_flags if f.startswith("--cfg ") or f.startswith("--cfg=")]
+
+ # Save BuildInfo if we find any (for build script output)
+ build_info = None
+ for dep in ctx.rule.attr.deps:
+ if BuildInfo in dep:
+ build_info = dep[BuildInfo]
+
+ dep_infos = [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.deps if RustAnalyzerInfo in dep]
+ if hasattr(ctx.rule.attr, "proc_macro_deps"):
+ dep_infos += [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.proc_macro_deps if RustAnalyzerInfo in dep]
+ if hasattr(ctx.rule.attr, "crate") and ctx.rule.attr.crate != None:
+ dep_infos.append(ctx.rule.attr.crate[RustAnalyzerInfo])
+
+ crate_spec = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_crate_spec")
+
+ crate_info = target[rust_common.crate_info]
+
+ rust_analyzer_info = RustAnalyzerInfo(
+ crate = crate_info,
+ cfgs = cfgs,
+ env = getattr(ctx.rule.attr, "rustc_env", {}),
+ deps = dep_infos,
+ crate_specs = depset(direct = [crate_spec], transitive = [dep.crate_specs for dep in dep_infos]),
+ proc_macro_dylib_path = find_proc_macro_dylib_path(toolchain, target),
+ build_info = build_info,
+ )
+
+ ctx.actions.write(
+ output = crate_spec,
+ content = json.encode(_create_single_crate(ctx, rust_analyzer_info)),
+ )
+
+ return [
+ rust_analyzer_info,
+ OutputGroupInfo(rust_analyzer_crate_spec = rust_analyzer_info.crate_specs),
+ ]
+
+def find_proc_macro_dylib_path(toolchain, target):
+ """Find the proc_macro_dylib_path of target. Returns None if target crate is not type proc-macro.
+
+ Args:
+ toolchain: The current rust toolchain.
+ target: The current target.
+ Returns:
+ (path): The path to the proc macro dylib, or None if this crate is not a proc-macro.
+ """
+ if target[rust_common.crate_info].type != "proc-macro":
+ return None
+
+ dylib_ext = system_to_dylib_ext(triple_to_system(toolchain.target_triple))
+ for action in target.actions:
+ for output in action.outputs.to_list():
+ if output.extension == dylib_ext[1:]:
+ return output.path
+
+ # Failed to find the dylib path inside a proc-macro crate.
+ # TODO: Should this be an error?
+ return None
+
+rust_analyzer_aspect = aspect(
+ attr_aspects = ["deps", "proc_macro_deps", "crate"],
+ implementation = _rust_analyzer_aspect_impl,
+ toolchains = [str(Label("//rust:toolchain"))],
+ incompatible_use_toolchain_transition = True,
+ doc = "Annotates rust rules with RustAnalyzerInfo later used to build a rust-project.json",
+)
+
+_exec_root_tmpl = "__EXEC_ROOT__/"
+
+def _crate_id(crate_info):
+ """Returns a unique stable identifier for a crate
+
+ Returns:
+ (string): This crate's unique stable id.
+ """
+ return "ID-" + crate_info.root.path
+
+def _create_single_crate(ctx, info):
+ """Creates a crate in the rust-project.json format.
+
+ Args:
+ ctx (ctx): The rule context
+ info (RustAnalyzerInfo): RustAnalyzerInfo for the current crate
+
+ Returns:
+ (dict) The crate rust-project.json representation
+ """
+ crate_name = info.crate.name
+ crate = dict()
+ crate_id = _crate_id(info.crate)
+ crate["crate_id"] = crate_id
+ crate["display_name"] = crate_name
+ crate["edition"] = info.crate.edition
+ crate["env"] = {}
+ crate["crate_type"] = info.crate.type
+
+ # Switch on external/ to determine if crates are in the workspace or remote.
+ # TODO: Some folks may want to override this for vendored dependencies.
+ root_path = info.crate.root.path
+ root_dirname = info.crate.root.dirname
+ if root_path.startswith("external/"):
+ crate["is_workspace_member"] = False
+ crate["root_module"] = _exec_root_tmpl + root_path
+ crate_root = _exec_root_tmpl + root_dirname
+ else:
+ crate["is_workspace_member"] = True
+ crate["root_module"] = root_path
+ crate_root = root_dirname
+
+ if info.build_info != None:
+ out_dir_path = info.build_info.out_dir.path
+ crate["env"].update({"OUT_DIR": _exec_root_tmpl + out_dir_path})
+ crate["source"] = {
+ # We have to tell rust-analyzer about our out_dir since it's not under the crate root.
+ "exclude_dirs": [],
+ "include_dirs": [crate_root, _exec_root_tmpl + out_dir_path],
+ }
+
+ # TODO: The only imagined use case is an env var holding a filename in the workspace passed to a
+ # macro like include_bytes!. Other use cases might exist that require more complex logic.
+ expand_targets = getattr(ctx.rule.attr, "data", []) + getattr(ctx.rule.attr, "compile_data", [])
+ crate["env"].update({k: ctx.expand_location(v, expand_targets) for k, v in info.env.items()})
+
+ # Omit when a crate appears to depend on itself (e.g. foo_test crates).
+ # It can happen a single source file is present in multiple crates - there can
+ # be a `rust_library` with a `lib.rs` file, and a `rust_test` for the `test`
+ # module in that file. Tests can declare more dependencies than what library
+ # had. Therefore we had to collect all RustAnalyzerInfos for a given crate
+ # and take deps from all of them.
+
+ # There's one exception - if the dependency is the same crate name as the
+ # the crate being processed, we don't add it as a dependency to itself. This is
+ # common and expected - `rust_test.crate` pointing to the `rust_library`.
+ crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
+ crate["cfg"] = info.cfgs
+ crate["target"] = find_toolchain(ctx).target_triple
+ if info.proc_macro_dylib_path != None:
+ crate["proc_macro_dylib_path"] = _exec_root_tmpl + info.proc_macro_dylib_path
+ return crate
+
+def _rust_analyzer_detect_sysroot_impl(ctx):
+ rust_toolchain = find_toolchain(ctx)
+
+ if not rust_toolchain.rustc_srcs:
+ fail(
+ "Current Rust toolchain doesn't contain rustc sources in `rustc_srcs` attribute.",
+ "These are needed by rust analyzer.",
+ "If you are using the default Rust toolchain, add `rust_repositories(include_rustc_srcs = True, ...).` to your WORKSPACE file.",
+ )
+ sysroot_src = rust_toolchain.rustc_srcs.label.package + "/library"
+ if rust_toolchain.rustc_srcs.label.workspace_root:
+ sysroot_src = _exec_root_tmpl + rust_toolchain.rustc_srcs.label.workspace_root + "/" + sysroot_src
+
+ sysroot_src_file = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_sysroot_src")
+ ctx.actions.write(
+ output = sysroot_src_file,
+ content = sysroot_src,
+ )
+
+ return [DefaultInfo(files = depset([sysroot_src_file]))]
+
+rust_analyzer_detect_sysroot = rule(
+ implementation = _rust_analyzer_detect_sysroot_impl,
+ toolchains = ["@rules_rust//rust:toolchain"],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Detect the sysroot and store in a file for use by the gen_rust_project tool.
+ """),
+)
+
+def _rust_analyzer_impl(_ctx):
+ pass
+
+rust_analyzer = rule(
+ attrs = {
+ "targets": attr.label_list(
+ aspects = [rust_analyzer_aspect],
+ doc = "List of all targets to be included in the index",
+ ),
+ },
+ implementation = _rust_analyzer_impl,
+ toolchains = [str(Label("//rust:toolchain"))],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Deprecated: gen_rust_project can now create a rust-project.json without a rust_analyzer rule.
+ """),
+)
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
new file mode 100644
index 0000000..596c10d
--- /dev/null
+++ b/rust/private/rustc.bzl
@@ -0,0 +1,1436 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Functionality for constructing actions that invoke the Rust compiler"""
+
+load(
+ "@bazel_tools//tools/build_defs/cc:action_names.bzl",
+ "CPP_LINK_EXECUTABLE_ACTION_NAME",
+)
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:providers.bzl", _BuildInfo = "BuildInfo")
+load("//rust/private:stamp.bzl", "is_stamping_enabled")
+load(
+ "//rust/private:utils.bzl",
+ "abs",
+ "expand_dict_value_locations",
+ "expand_list_element_locations",
+ "find_cc_toolchain",
+ "get_lib_name",
+ "get_preferred_artifact",
+ "is_exec_configuration",
+ "make_static_lib_symlink",
+ "relativize",
+)
+
+BuildInfo = _BuildInfo
+
+AliasableDepInfo = provider(
+ doc = "A provider mapping an alias name to a Crate's information.",
+ fields = {
+ "dep": "CrateInfo",
+ "name": "str",
+ },
+)
+
+_error_format_values = ["human", "json", "short"]
+
+ErrorFormatInfo = provider(
+ doc = "Set the --error-format flag for all rustc invocations",
+ fields = {"error_format": "(string) [" + ", ".join(_error_format_values) + "]"},
+)
+
+ExtraRustcFlagsInfo = provider(
+ doc = "Pass each value as an additional flag to non-exec rustc invocations",
+ fields = {"extra_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"},
+)
+
+ExtraExecRustcFlagsInfo = provider(
+ doc = "Pass each value as an additional flag to exec rustc invocations",
+ fields = {"extra_exec_rustc_flags": "List[string] Extra flags to pass to rustc in exec configuration"},
+)
+
+def _get_rustc_env(attr, toolchain, crate_name):
+ """Gathers rustc environment variables
+
+ Args:
+ attr (struct): The current target's attributes
+ toolchain (rust_toolchain): The current target's rust toolchain context
+ crate_name (str): The name of the crate to be compiled
+
+ Returns:
+ dict: Rustc environment variables
+ """
+ version = attr.version if hasattr(attr, "version") else "0.0.0"
+ major, minor, patch = version.split(".", 2)
+ if "-" in patch:
+ patch, pre = patch.split("-", 1)
+ else:
+ pre = ""
+ return {
+ "CARGO_CFG_TARGET_ARCH": toolchain.target_arch,
+ "CARGO_CFG_TARGET_OS": toolchain.os,
+ "CARGO_CRATE_NAME": crate_name,
+ "CARGO_PKG_AUTHORS": "",
+ "CARGO_PKG_DESCRIPTION": "",
+ "CARGO_PKG_HOMEPAGE": "",
+ "CARGO_PKG_NAME": attr.name,
+ "CARGO_PKG_VERSION": version,
+ "CARGO_PKG_VERSION_MAJOR": major,
+ "CARGO_PKG_VERSION_MINOR": minor,
+ "CARGO_PKG_VERSION_PATCH": patch,
+ "CARGO_PKG_VERSION_PRE": pre,
+ }
+
+def get_compilation_mode_opts(ctx, toolchain):
+ """Gathers rustc flags for the current compilation mode (opt/debug)
+
+ Args:
+ ctx (ctx): The current rule's context object
+ toolchain (rust_toolchain): The current rule's `rust_toolchain`
+
+ Returns:
+ struct: See `_rust_toolchain_impl` for more details
+ """
+ comp_mode = ctx.var["COMPILATION_MODE"]
+ if not comp_mode in toolchain.compilation_mode_opts:
+ fail("Unrecognized compilation mode {} for toolchain.".format(comp_mode))
+
+ return toolchain.compilation_mode_opts[comp_mode]
+
+def _are_linkstamps_supported(feature_configuration, has_grep_includes):
+ # Are linkstamps supported by the C++ toolchain?
+ return (cc_common.is_enabled(feature_configuration = feature_configuration, feature_name = "linkstamps") and
+ # Is Bazel recent enough to support Starlark linkstamps?
+ hasattr(cc_common, "register_linkstamp_compile_action") and
+ # The current rule doesn't define _grep_includes attribute; this
+ # attribute is required for compiling linkstamps.
+ has_grep_includes)
+
+def _should_use_pic(cc_toolchain, feature_configuration, crate_type):
+ if crate_type in ("cdylib" or "dylib"):
+ return cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration)
+ return False
+
+def collect_deps(
+ deps,
+ proc_macro_deps,
+ aliases,
+ are_linkstamps_supported = False):
+ """Walks through dependencies and collects the transitive dependencies.
+
+ Args:
+ deps (list): The deps from ctx.attr.deps.
+ proc_macro_deps (list): The proc_macro deps from ctx.attr.proc_macro_deps.
+ aliases (dict): A dict mapping aliased targets to their actual Crate information.
+ are_linkstamps_supported (bool): Whether the current rule and the toolchain support building linkstamps..
+
+ Returns:
+ tuple: Returns a tuple of:
+ DepInfo,
+ BuildInfo,
+ linkstamps (depset[CcLinkstamp]): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
+
+ """
+ direct_crates = []
+ transitive_crates = []
+ transitive_noncrates = []
+ transitive_build_infos = []
+ transitive_link_search_paths = []
+ build_info = None
+ linkstamps = []
+ transitive_crate_outputs = []
+
+ aliases = {k.label: v for k, v in aliases.items()}
+ for dep in depset(transitive = [deps, proc_macro_deps]).to_list():
+ (crate_info, dep_info) = _get_crate_and_dep_info(dep)
+ cc_info = _get_cc_info(dep)
+ dep_build_info = _get_build_info(dep)
+
+ if cc_info and are_linkstamps_supported:
+ linkstamps.append(cc_info.linking_context.linkstamps())
+
+ if crate_info:
+ # This dependency is a rust_library
+
+ # When crate_info.owner is set, we use it. When the dep type is Target we get the
+ # label from dep.label
+ owner = getattr(crate_info, "owner", dep.label if type(dep) == "Target" else None)
+
+ direct_crates.append(AliasableDepInfo(
+ name = aliases.get(owner, crate_info.name),
+ dep = crate_info,
+ ))
+
+ transitive_crates.append(depset([crate_info], transitive = [dep_info.transitive_crates]))
+ transitive_crate_outputs.append(
+ depset(
+ [crate_info.output],
+ transitive = [dep_info.transitive_crate_outputs],
+ ),
+ )
+ transitive_noncrates.append(dep_info.transitive_noncrates)
+ transitive_build_infos.append(dep_info.transitive_build_infos)
+ transitive_link_search_paths.append(dep_info.link_search_path_files)
+
+ elif cc_info:
+ # This dependency is a cc_library
+ transitive_noncrates.append(cc_info.linking_context.linker_inputs)
+ elif dep_build_info:
+ if build_info:
+ fail("Several deps are providing build information, " +
+ "only one is allowed in the dependencies")
+ build_info = dep_build_info
+ transitive_build_infos.append(depset([build_info]))
+ transitive_link_search_paths.append(depset([build_info.link_search_paths]))
+ else:
+ fail("rust targets can only depend on rust_library, rust_*_library or cc_library " +
+ "targets.")
+
+ transitive_crates_depset = depset(transitive = transitive_crates)
+
+ return (
+ rust_common.dep_info(
+ direct_crates = depset(direct_crates),
+ transitive_crates = transitive_crates_depset,
+ transitive_noncrates = depset(
+ transitive = transitive_noncrates,
+ order = "topological", # dylib link flag ordering matters.
+ ),
+ transitive_crate_outputs = depset(transitive = transitive_crate_outputs),
+ transitive_build_infos = depset(transitive = transitive_build_infos),
+ link_search_path_files = depset(transitive = transitive_link_search_paths),
+ dep_env = build_info.dep_env if build_info else None,
+ ),
+ build_info,
+ depset(transitive = linkstamps),
+ )
+
+def _collect_libs_from_linker_inputs(linker_inputs, use_pic):
+ # TODO: We could let the user choose how to link, instead of always preferring to link static libraries.
+ return [
+ get_preferred_artifact(lib, use_pic)
+ for li in linker_inputs
+ for lib in li.libraries
+ ]
+
+def _get_crate_and_dep_info(dep):
+ if type(dep) == "Target" and rust_common.crate_info in dep:
+ return (dep[rust_common.crate_info], dep[rust_common.dep_info])
+ elif type(dep) == "struct" and hasattr(dep, "crate_info"):
+ return (dep.crate_info, dep.dep_info)
+ return (None, None)
+
+def _get_cc_info(dep):
+ if type(dep) == "Target" and CcInfo in dep:
+ return dep[CcInfo]
+ elif type(dep) == "struct" and hasattr(dep, "cc_info"):
+ return dep.cc_info
+ return None
+
+def _get_build_info(dep):
+ if type(dep) == "Target" and BuildInfo in dep:
+ return dep[BuildInfo]
+ elif type(dep) == "struct" and hasattr(dep, "build_info"):
+ return dep.build_info
+ return None
+
+def get_cc_user_link_flags(ctx):
+ """Get the current target's linkopt flags
+
+ Args:
+ ctx (ctx): The current rule's context object
+
+ Returns:
+ depset: The flags passed to Bazel by --linkopt option.
+ """
+ return ctx.fragments.cpp.linkopts
+
+def get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths):
+ """Gathers cc_common linker information
+
+ Args:
+ ctx (ctx): The current target's context object
+ attr (struct): Attributes to use in gathering linker args
+ cc_toolchain (CcToolchain): cc_toolchain for which we are creating build variables.
+ feature_configuration (FeatureConfiguration): Feature configuration to be queried.
+ rpaths (depset): Depset of directories where loader will look for libraries at runtime.
+
+
+ Returns:
+ tuple: A tuple of the following items:
+ - (str): The tool path for given action.
+ - (sequence): A flattened command line flags for given action.
+ - (dict): Environment variables to be set for given action.
+ """
+ user_link_flags = get_cc_user_link_flags(ctx)
+
+ # Add linkopt's from dependencies. This includes linkopts from transitive
+ # dependencies since they get merged up.
+ for dep in getattr(attr, "deps", []):
+ if CcInfo in dep and dep[CcInfo].linking_context:
+ for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list():
+ for flag in linker_input.user_link_flags:
+ user_link_flags.append(flag)
+ link_variables = cc_common.create_link_variables(
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ is_linking_dynamic_library = False,
+ runtime_library_search_directories = rpaths,
+ user_link_flags = user_link_flags,
+ )
+ link_args = cc_common.get_memory_inefficient_command_line(
+ feature_configuration = feature_configuration,
+ action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+ variables = link_variables,
+ )
+ link_env = cc_common.get_environment_variables(
+ feature_configuration = feature_configuration,
+ action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+ variables = link_variables,
+ )
+ ld = cc_common.get_tool_for_action(
+ feature_configuration = feature_configuration,
+ action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+ )
+
+ return ld, link_args, link_env
+
+def _process_build_scripts(
+ build_info,
+ dep_info,
+ compile_inputs):
+ """Gathers the outputs from a target's `cargo_build_script` action.
+
+ Args:
+ build_info (BuildInfo): The target Build's dependency info.
+ dep_info (DepInfo): The Depinfo provider form the target Crate's set of inputs.
+ compile_inputs (depset): A set of all files that will participate in the build.
+
+ Returns:
+ tuple: A tuple: A tuple of the following items:
+ - (depset[File]): A list of all build info `OUT_DIR` File objects
+ - (str): The `OUT_DIR` of the current build info
+ - (File): An optional path to a generated environment file from a `cargo_build_script` target
+ - (depset[File]): All direct and transitive build flags from the current build info.
+ """
+ extra_inputs, out_dir, build_env_file, build_flags_files = _create_extra_input_args(build_info, dep_info)
+ compile_inputs = depset(transitive = [extra_inputs, compile_inputs])
+ return compile_inputs, out_dir, build_env_file, build_flags_files
+
+def _symlink_for_ambiguous_lib(actions, toolchain, crate_info, lib):
+ """Constructs a disambiguating symlink for a library dependency.
+
+ Args:
+ actions (Actions): The rule's context actions object.
+ toolchain: The Rust toolchain object.
+ crate_info (CrateInfo): The target crate's info.
+ lib (File): The library to symlink to.
+
+ Returns:
+ (File): The disambiguating symlink for the library.
+ """
+ # FIXME: Once the relative order part of the native-link-modifiers rustc
+ # feature is stable, we should be able to eliminate the need to construct
+ # symlinks by passing the full paths to the libraries.
+ # https://github.com/rust-lang/rust/issues/81490.
+
+ # Take the absolute value of hash() since it could be negative.
+ path_hash = abs(hash(lib.path))
+ lib_name = get_lib_name(lib)
+
+ prefix = "lib"
+ extension = ".a"
+ if toolchain.os.startswith("windows"):
+ prefix = ""
+ extension = ".lib"
+
+ # Ensure the symlink follows the lib<name>.a pattern on Unix-like platforms
+ # or <name>.lib on Windows.
+ # Add a hash of the original library path to disambiguate libraries with the same basename.
+ symlink_name = "{}{}-{}{}".format(prefix, lib_name, path_hash, extension)
+
+ # Add the symlink to a target crate-specific _ambiguous_libs/ subfolder,
+ # to avoid possible collisions with sibling crates that may depend on the
+ # same ambiguous libraries.
+ symlink = actions.declare_file("_ambiguous_libs/" + crate_info.output.basename + "/" + symlink_name)
+ actions.symlink(
+ output = symlink,
+ target_file = lib,
+ progress_message = "Creating symlink to ambiguous lib: {}".format(lib.path),
+ )
+ return symlink
+
+def _disambiguate_libs(actions, toolchain, crate_info, dep_info, use_pic):
+ """Constructs disambiguating symlinks for ambiguous library dependencies.
+
+ The symlinks are all created in a _ambiguous_libs/ subfolder specific to
+ the target crate to avoid possible collisions with sibling crates that may
+ depend on the same ambiguous libraries.
+
+ Args:
+ actions (Actions): The rule's context actions object.
+ toolchain: The Rust toolchain object.
+ crate_info (CrateInfo): The target crate's info.
+ dep_info: (DepInfo): The target crate's dependency info.
+ use_pic: (boolean): Whether the build should use PIC.
+
+ Returns:
+ dict[String, File]: A mapping from ambiguous library paths to their
+ disambiguating symlink.
+ """
+ # FIXME: Once the relative order part of the native-link-modifiers rustc
+ # feature is stable, we should be able to eliminate the need to construct
+ # symlinks by passing the full paths to the libraries.
+ # https://github.com/rust-lang/rust/issues/81490.
+
+ # A dictionary from file paths of ambiguous libraries to the corresponding
+ # symlink.
+ ambiguous_libs = {}
+
+ # A dictionary maintaining a mapping from preferred library name to the
+ # last visited artifact with that name.
+ visited_libs = {}
+ for link_input in dep_info.transitive_noncrates.to_list():
+ for lib in link_input.libraries:
+ # FIXME: Dynamic libs are not disambiguated right now, there are
+ # cases where those have a non-standard name with version (e.g.,
+ # //test/unit/versioned_libs). We hope that the link modifiers
+ # stabilization will come before we need to make this work.
+ if _is_dylib(lib):
+ continue
+ artifact = get_preferred_artifact(lib, use_pic)
+ name = get_lib_name(artifact)
+
+ # On Linux-like platforms, normally library base names start with
+ # `lib`, following the pattern `lib[name].(a|lo)` and we pass
+ # -lstatic=name.
+ # On Windows, the base name looks like `name.lib` and we pass
+ # -lstatic=name.
+ # FIXME: Under the native-link-modifiers unstable rustc feature,
+ # we could use -lstatic:+verbatim instead.
+ needs_symlink_to_standardize_name = (
+ (toolchain.os.startswith("linux") or toolchain.os.startswith("mac") or toolchain.os.startswith("darwin")) and
+ artifact.basename.endswith(".a") and not artifact.basename.startswith("lib")
+ ) or (
+ toolchain.os.startswith("windows") and not artifact.basename.endswith(".lib")
+ )
+
+ # Detect cases where we need to disambiguate library dependencies
+ # by constructing symlinks.
+ if (
+ needs_symlink_to_standardize_name or
+ # We have multiple libraries with the same name.
+ (name in visited_libs and visited_libs[name].path != artifact.path)
+ ):
+ # Disambiguate the previously visited library (if we just detected
+ # that it is ambiguous) and the current library.
+ if name in visited_libs:
+ old_path = visited_libs[name].path
+ if old_path not in ambiguous_libs:
+ ambiguous_libs[old_path] = _symlink_for_ambiguous_lib(actions, toolchain, crate_info, visited_libs[name])
+ ambiguous_libs[artifact.path] = _symlink_for_ambiguous_lib(actions, toolchain, crate_info, artifact)
+
+ visited_libs[name] = artifact
+ return ambiguous_libs
+
+def collect_inputs(
+ ctx,
+ file,
+ files,
+ linkstamps,
+ toolchain,
+ cc_toolchain,
+ feature_configuration,
+ crate_info,
+ dep_info,
+ build_info,
+ stamp = False):
+ """Gather's the inputs and required input information for a rustc action
+
+ Args:
+ ctx (ctx): The rule's context object.
+ file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
+ files (list): A list of all inputs (`ctx.files`).
+ linkstamps (depset): A depset of CcLinkstamps that need to be compiled and linked into all linked binaries.
+ toolchain (rust_toolchain): The current `rust_toolchain`.
+ cc_toolchain (CcToolchainInfo): The current `cc_toolchain`.
+ feature_configuration (FeatureConfiguration): Feature configuration to be queried.
+ crate_info (CrateInfo): The Crate information of the crate to process build scripts for.
+ dep_info (DepInfo): The target Crate's dependency information.
+ build_info (BuildInfo): The target Crate's build settings.
+ stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
+ https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
+
+ Returns:
+ tuple: A tuple: A tuple of the following items:
+ - (list): A list of all build info `OUT_DIR` File objects
+ - (str): The `OUT_DIR` of the current build info
+ - (File): An optional path to a generated environment file from a `cargo_build_script` target
+ - (depset[File]): All direct and transitive build flag files from the current build info
+ - (list[File]): Linkstamp outputs
+ - (dict[String, File]): Ambiguous libs, see `_disambiguate_libs`.
+ """
+ linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
+
+ linker_depset = cc_toolchain.all_files
+
+ use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
+
+ # Pass linker inputs only for linking-like actions, not for example where
+ # the output is rlib. This avoids quadratic behavior where transitive noncrates are
+ # flattened on each transitive rust_library dependency.
+ additional_transitive_inputs = []
+ ambiguous_libs = {}
+ if crate_info.type in ("staticlib", "proc-macro"):
+ additional_transitive_inputs = _collect_libs_from_linker_inputs(
+ dep_info.transitive_noncrates.to_list(),
+ use_pic,
+ )
+ elif crate_info.type in ("bin", "dylib", "cdylib"):
+ linker_inputs = dep_info.transitive_noncrates.to_list()
+ ambiguous_libs = _disambiguate_libs(ctx.actions, toolchain, crate_info, dep_info, use_pic)
+ additional_transitive_inputs = _collect_libs_from_linker_inputs(linker_inputs, use_pic) + [
+ additional_input
+ for linker_input in linker_inputs
+ for additional_input in linker_input.additional_inputs
+ ] + ambiguous_libs.values()
+
+ # Compute linkstamps. Use the inputs of the binary as inputs to the
+ # linkstamp action to ensure linkstamps are rebuilt whenever binary inputs
+ # change.
+ linkstamp_outs = []
+
+ nolinkstamp_compile_inputs = depset(
+ getattr(files, "data", []) +
+ ([build_info.rustc_env, build_info.flags] if build_info else []) +
+ ([toolchain.target_json] if toolchain.target_json else []) +
+ ([] if linker_script == None else [linker_script]),
+ transitive = [
+ linker_depset,
+ crate_info.srcs,
+ dep_info.transitive_crate_outputs,
+ depset(additional_transitive_inputs),
+ crate_info.compile_data,
+ toolchain.all_files,
+ ],
+ )
+
+ if crate_info.type in ("bin", "cdylib"):
+ # There is no other way to register an action for each member of a depset than
+ # flattening the depset as of 2021-10-12. Luckily, usually there is only one linkstamp
+ # in a build, and we only flatten the list on binary targets that perform transitive linking,
+ # so it's extremely unlikely that this call to `to_list()` will ever be a performance
+ # problem.
+ for linkstamp in linkstamps.to_list():
+ # The linkstamp output path is based on the binary crate
+ # name and the input linkstamp path. This is to disambiguate
+ # the linkstamp outputs produced by multiple binary crates
+ # that depend on the same linkstamp. We use the same pattern
+ # for the output name as the one used by native cc rules.
+ out_name = "_objs/" + crate_info.output.basename + "/" + linkstamp.file().path[:-len(linkstamp.file().extension)] + "o"
+ linkstamp_out = ctx.actions.declare_file(out_name)
+ linkstamp_outs.append(linkstamp_out)
+ cc_common.register_linkstamp_compile_action(
+ actions = ctx.actions,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ grep_includes = ctx.file._grep_includes,
+ source_file = linkstamp.file(),
+ output_file = linkstamp_out,
+ compilation_inputs = linkstamp.hdrs(),
+ inputs_for_validation = nolinkstamp_compile_inputs,
+ label_replacement = str(ctx.label),
+ output_replacement = crate_info.output.path,
+ )
+
+ # If stamping is enabled include the volatile status info file
+ stamp_info = [ctx.version_file] if stamp else []
+
+ compile_inputs = depset(
+ linkstamp_outs + stamp_info,
+ transitive = [
+ nolinkstamp_compile_inputs,
+ ],
+ )
+
+ build_env_files = getattr(files, "rustc_env_files", [])
+ compile_inputs, out_dir, build_env_file, build_flags_files = _process_build_scripts(build_info, dep_info, compile_inputs)
+ if build_env_file:
+ build_env_files = [f for f in build_env_files] + [build_env_file]
+ compile_inputs = depset(build_env_files, transitive = [compile_inputs])
+
+ return compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs
+
+def construct_arguments(
+ ctx,
+ attr,
+ file,
+ toolchain,
+ tool_path,
+ cc_toolchain,
+ feature_configuration,
+ crate_info,
+ dep_info,
+ linkstamp_outs,
+ ambiguous_libs,
+ output_hash,
+ rust_flags,
+ out_dir,
+ build_env_files,
+ build_flags_files,
+ emit = ["dep-info", "link"],
+ force_all_deps_direct = False,
+ force_link = False,
+ stamp = False,
+ remap_path_prefix = "."):
+ """Builds an Args object containing common rustc flags
+
+ Args:
+ ctx (ctx): The rule's context object
+ attr (struct): The attributes for the target. These may be different from ctx.attr in an aspect context.
+ file (struct): A struct containing files defined in label type attributes marked as `allow_single_file`.
+ toolchain (rust_toolchain): The current target's `rust_toolchain`
+ tool_path (str): Path to rustc
+ cc_toolchain (CcToolchain): The CcToolchain for the current target.
+ feature_configuration (FeatureConfiguration): Class used to construct command lines from CROSSTOOL features.
+ crate_info (CrateInfo): The CrateInfo provider of the target crate
+ dep_info (DepInfo): The DepInfo provider of the target crate
+ linkstamp_outs (list): Linkstamp outputs of native dependencies
+ ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
+ output_hash (str): The hashed path of the crate root
+ rust_flags (list): Additional flags to pass to rustc
+ out_dir (str): The path to the output directory for the target Crate.
+ build_env_files (list): Files containing rustc environment variables, for instance from `cargo_build_script` actions.
+ build_flags_files (depset): The output files of a `cargo_build_script` actions containing rustc build flags
+ emit (list): Values for the --emit flag to rustc.
+ force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
+ to the commandline as opposed to -L.
+ force_link (bool, optional): Whether to add link flags to the command regardless of `emit`.
+ stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
+ https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
+ remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to a falsey value, no prefix will be set.
+
+ Returns:
+ tuple: A tuple of the following items
+ - (struct): A struct of arguments used to run the `Rustc` action
+ - process_wrapper_flags (Args): Arguments for the process wrapper
+ - rustc_path (Args): Arguments for invoking rustc via the process wrapper
+ - rustc_flags (Args): Rust flags for the Rust compiler
+ - all (list): A list of all `Args` objects in the order listed above.
+ This is to be passed to the `arguments` parameter of actions
+ - (dict): Common rustc environment variables
+ """
+ output_dir = getattr(crate_info.output, "dirname", None)
+ linker_script = getattr(file, "linker_script", None)
+
+ env = _get_rustc_env(attr, toolchain, crate_info.name)
+
+ # Wrapper args first
+ process_wrapper_flags = ctx.actions.args()
+
+ for build_env_file in build_env_files:
+ process_wrapper_flags.add("--env-file", build_env_file)
+
+ process_wrapper_flags.add_all(build_flags_files, before_each = "--arg-file")
+
+ # Certain rust build processes expect to find files from the environment
+ # variable `$CARGO_MANIFEST_DIR`. Examples of this include pest, tera,
+ # asakuma.
+ #
+ # The compiler and by extension proc-macros see the current working
+ # directory as the Bazel exec root. This is what `$CARGO_MANIFEST_DIR`
+ # would default to but is often the wrong value (e.g. if the source is in a
+ # sub-package or if we are building something in an external repository).
+ # Hence, we need to set `CARGO_MANIFEST_DIR` explicitly.
+ #
+ # Since we cannot get the `exec_root` from starlark, we cheat a little and
+ # use `${pwd}` which resolves the `exec_root` at action execution time.
+ process_wrapper_flags.add("--subst", "pwd=${pwd}")
+
+ # If stamping is enabled, enable the functionality in the process wrapper
+ if stamp:
+ process_wrapper_flags.add("--volatile-status-file", ctx.version_file)
+
+ # Both ctx.label.workspace_root and ctx.label.package are relative paths
+ # and either can be empty strings. Avoid trailing/double slashes in the path.
+ components = "${{pwd}}/{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/")
+ env["CARGO_MANIFEST_DIR"] = "/".join([c for c in components if c])
+
+ if out_dir != None:
+ env["OUT_DIR"] = "${pwd}/" + out_dir
+
+ # Handle that the binary name and crate name may be different.
+ #
+ # If a target name contains a - then cargo (and rules_rust) will generate a
+ # crate name with _ instead. Accordingly, rustc will generate a output
+ # file (executable, or rlib, or whatever) with _ not -. But when cargo
+ # puts a binary in the target/${config} directory, and sets environment
+ # variables like `CARGO_BIN_EXE_${binary_name}` it will use the - version
+ # not the _ version. So we rename the rustc-generated file (with _s) to
+ # have -s if needed.
+ emit_with_paths = emit
+ if crate_info.type == "bin" and crate_info.output != None:
+ generated_file = crate_info.name + toolchain.binary_ext
+ src = "/".join([crate_info.output.dirname, generated_file])
+ dst = crate_info.output.path
+ if src != dst:
+ emit_with_paths = [("link=" + dst if val == "link" else val) for val in emit]
+
+ # Arguments for launching rustc from the process wrapper
+ rustc_path = ctx.actions.args()
+ rustc_path.add("--")
+ rustc_path.add(tool_path)
+
+ # Rustc arguments
+ rustc_flags = ctx.actions.args()
+ rustc_flags.set_param_file_format("multiline")
+ rustc_flags.use_param_file("@%s", use_always = False)
+ rustc_flags.add(crate_info.root)
+ rustc_flags.add("--crate-name=" + crate_info.name)
+ rustc_flags.add("--crate-type=" + crate_info.type)
+ if hasattr(attr, "_error_format"):
+ rustc_flags.add("--error-format=" + attr._error_format[ErrorFormatInfo].error_format)
+
+ # Mangle symbols to disambiguate crates with the same name
+ extra_filename = "-" + output_hash if output_hash else ""
+ rustc_flags.add("--codegen=metadata=" + extra_filename)
+ if output_dir:
+ rustc_flags.add("--out-dir=" + output_dir)
+ rustc_flags.add("--codegen=extra-filename=" + extra_filename)
+
+ compilation_mode = get_compilation_mode_opts(ctx, toolchain)
+ rustc_flags.add("--codegen=opt-level=" + compilation_mode.opt_level)
+ rustc_flags.add("--codegen=debuginfo=" + compilation_mode.debug_info)
+
+ # For determinism to help with build distribution and such
+ if remap_path_prefix:
+ rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix))
+
+ if emit:
+ rustc_flags.add("--emit=" + ",".join(emit_with_paths))
+ rustc_flags.add("--color=always")
+ rustc_flags.add("--target=" + toolchain.target_flag_value)
+ if hasattr(attr, "crate_features"):
+ rustc_flags.add_all(getattr(attr, "crate_features"), before_each = "--cfg", format_each = 'feature="%s"')
+ if linker_script:
+ rustc_flags.add(linker_script.path, format = "--codegen=link-arg=-T%s")
+
+ # Gets the paths to the folders containing the standard library (or libcore)
+ rust_std_paths = toolchain.rust_std_paths.to_list()
+
+ # Tell Rustc where to find the standard library
+ rustc_flags.add_all(rust_std_paths, before_each = "-L", format_each = "%s")
+ rustc_flags.add_all(rust_flags)
+
+ # Deduplicate data paths due to https://github.com/bazelbuild/bazel/issues/14681
+ data_paths = depset(direct = getattr(attr, "data", []) + getattr(attr, "compile_data", [])).to_list()
+
+ rustc_flags.add_all(
+ expand_list_element_locations(
+ ctx,
+ getattr(attr, "rustc_flags", []),
+ data_paths,
+ ),
+ )
+ add_edition_flags(rustc_flags, crate_info)
+
+ # Link!
+ if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or force_link:
+ # Rust's built-in linker can handle linking wasm files. We don't want to attempt to use the cc
+ # linker since it won't understand.
+ if toolchain.target_arch != "wasm32":
+ if output_dir:
+ use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
+ rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic)
+ else:
+ rpaths = depset([])
+ ld, link_args, link_env = get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths)
+ env.update(link_env)
+ rustc_flags.add("--codegen=linker=" + ld)
+ rustc_flags.add_joined("--codegen", link_args, join_with = " ", format_joined = "link-args=%s")
+
+ _add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, ambiguous_libs, crate_info.type, toolchain, cc_toolchain, feature_configuration)
+
+ # These always need to be added, even if not linking this crate.
+ add_crate_link_flags(rustc_flags, dep_info, force_all_deps_direct)
+
+ needs_extern_proc_macro_flag = "proc-macro" in [crate_info.type, crate_info.wrapped_crate_type] and \
+ crate_info.edition != "2015"
+ if needs_extern_proc_macro_flag:
+ rustc_flags.add("--extern")
+ rustc_flags.add("proc_macro")
+
+ # Make bin crate data deps available to tests.
+ for data in getattr(attr, "data", []):
+ if rust_common.crate_info in data:
+ dep_crate_info = data[rust_common.crate_info]
+ if dep_crate_info.type == "bin":
+ # Trying to make CARGO_BIN_EXE_{} canonical across platform by strip out extension if exists
+ env_basename = dep_crate_info.output.basename[:-(1 + len(dep_crate_info.output.extension))] if len(dep_crate_info.output.extension) > 0 else dep_crate_info.output.basename
+ env["CARGO_BIN_EXE_" + env_basename] = dep_crate_info.output.short_path
+
+ # Update environment with user provided variables.
+ env.update(expand_dict_value_locations(
+ ctx,
+ crate_info.rustc_env,
+ data_paths,
+ ))
+
+ # Ensure the sysroot is set for the target platform
+ env["SYSROOT"] = toolchain.sysroot
+
+ if toolchain._rename_first_party_crates:
+ env["RULES_RUST_THIRD_PARTY_DIR"] = toolchain._third_party_dir
+
+ # extra_rustc_flags apply to the target configuration, not the exec configuration.
+ if hasattr(ctx.attr, "_extra_rustc_flags") and not is_exec_configuration(ctx):
+ rustc_flags.add_all(ctx.attr._extra_rustc_flags[ExtraRustcFlagsInfo].extra_rustc_flags)
+
+ if hasattr(ctx.attr, "_extra_exec_rustc_flags") and is_exec_configuration(ctx):
+ rustc_flags.add_all(ctx.attr._extra_exec_rustc_flags[ExtraExecRustcFlagsInfo].extra_exec_rustc_flags)
+
+ # Create a struct which keeps the arguments separate so each may be tuned or
+ # replaced where necessary
+ args = struct(
+ process_wrapper_flags = process_wrapper_flags,
+ rustc_path = rustc_path,
+ rustc_flags = rustc_flags,
+ all = [process_wrapper_flags, rustc_path, rustc_flags],
+ )
+
+ return args, env
+
+def rustc_compile_action(
+ ctx,
+ attr,
+ toolchain,
+ crate_info,
+ output_hash = None,
+ rust_flags = [],
+ force_all_deps_direct = False):
+ """Create and run a rustc compile action based on the current rule's attributes
+
+ Args:
+ ctx (ctx): The rule's context object
+ attr (struct): Attributes to use for the rust compile action
+ toolchain (rust_toolchain): The current `rust_toolchain`
+ crate_info (CrateInfo): The CrateInfo provider for the current target.
+ output_hash (str, optional): The hashed path of the crate root. Defaults to None.
+ rust_flags (list, optional): Additional flags to pass to rustc. Defaults to [].
+ force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
+ to the commandline as opposed to -L.
+
+ Returns:
+ list: A list of the following providers:
+ - (CrateInfo): info for the crate we just built; same as `crate_info` parameter.
+ - (DepInfo): The transitive dependencies of this crate.
+ - (DefaultInfo): The output file for this crate, and its runfiles.
+ """
+ cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+
+ dep_info, build_info, linkstamps = collect_deps(
+ deps = crate_info.deps,
+ proc_macro_deps = crate_info.proc_macro_deps,
+ aliases = crate_info.aliases,
+ are_linkstamps_supported = _are_linkstamps_supported(
+ feature_configuration = feature_configuration,
+ has_grep_includes = hasattr(ctx.attr, "_grep_includes"),
+ ),
+ )
+
+ # Determine if the build is currently running with --stamp
+ stamp = is_stamping_enabled(attr)
+
+ compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
+ ctx = ctx,
+ file = ctx.file,
+ files = ctx.files,
+ linkstamps = linkstamps,
+ toolchain = toolchain,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ crate_info = crate_info,
+ dep_info = dep_info,
+ build_info = build_info,
+ stamp = stamp,
+ )
+
+ args, env_from_args = construct_arguments(
+ ctx = ctx,
+ attr = attr,
+ file = ctx.file,
+ toolchain = toolchain,
+ tool_path = toolchain.rustc.path,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ crate_info = crate_info,
+ dep_info = dep_info,
+ linkstamp_outs = linkstamp_outs,
+ ambiguous_libs = ambiguous_libs,
+ output_hash = output_hash,
+ rust_flags = rust_flags,
+ out_dir = out_dir,
+ build_env_files = build_env_files,
+ build_flags_files = build_flags_files,
+ force_all_deps_direct = force_all_deps_direct,
+ stamp = stamp,
+ )
+
+ env = dict(ctx.configuration.default_shell_env)
+ env.update(env_from_args)
+
+ if hasattr(attr, "version") and attr.version != "0.0.0":
+ formatted_version = " v{}".format(attr.version)
+ else:
+ formatted_version = ""
+
+ outputs = [crate_info.output]
+
+ # For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the
+ # interface library that rustc generates in the output files.
+ interface_library = None
+ if toolchain.os == "windows" and crate_info.type == "cdylib":
+ # Rustc generates the import library with a `.dll.lib` extension rather than the usual `.lib` one that msvc
+ # expects (see https://github.com/rust-lang/rust/pull/29520 for more context).
+ interface_library = ctx.actions.declare_file(crate_info.output.basename + ".lib")
+ outputs.append(interface_library)
+
+ # The action might generate extra output that we don't want to include in the `DefaultInfo` files.
+ action_outputs = list(outputs)
+
+ # Rustc generates a pdb file (on Windows) or a dsym folder (on macos) so provide it in an output group for crate
+ # types that benefit from having debug information in a separate file.
+ pdb_file = None
+ dsym_folder = None
+ if crate_info.type in ("cdylib", "bin") and not crate_info.is_test:
+ if toolchain.os == "windows":
+ pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb")
+ action_outputs.append(pdb_file)
+ elif toolchain.os == "darwin":
+ dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM")
+ action_outputs.append(dsym_folder)
+
+ # This uses startswith as on windows the basename will be process_wrapper_fake.exe.
+ if not ctx.executable._process_wrapper.basename.startswith("process_wrapper_fake"):
+ # Run as normal
+ ctx.actions.run(
+ executable = ctx.executable._process_wrapper,
+ inputs = compile_inputs,
+ outputs = action_outputs,
+ env = env,
+ arguments = args.all,
+ mnemonic = "Rustc",
+ progress_message = "Compiling Rust {} {}{} ({} files)".format(
+ crate_info.type,
+ ctx.label.name,
+ formatted_version,
+ len(crate_info.srcs.to_list()),
+ ),
+ )
+ else:
+ # Run without process_wrapper
+ if build_env_files or build_flags_files or stamp:
+ fail("build_env_files, build_flags_files, stamp are not supported if use_process_wrapper is False")
+ ctx.actions.run(
+ executable = toolchain.rustc,
+ inputs = compile_inputs,
+ outputs = action_outputs,
+ env = env,
+ arguments = [args.rustc_flags],
+ mnemonic = "Rustc",
+ progress_message = "Compiling Rust (without process_wrapper) {} {}{} ({} files)".format(
+ crate_info.type,
+ ctx.label.name,
+ formatted_version,
+ len(crate_info.srcs.to_list()),
+ ),
+ )
+
+ runfiles = ctx.runfiles(
+ files = getattr(ctx.files, "data", []),
+ collect_data = True,
+ )
+
+ # TODO: Remove after some resolution to
+ # https://github.com/bazelbuild/rules_rust/issues/771
+ out_binary = getattr(attr, "out_binary", False)
+
+ providers = [
+ crate_info,
+ dep_info,
+ DefaultInfo(
+ # nb. This field is required for cc_library to depend on our output.
+ files = depset(outputs),
+ runfiles = runfiles,
+ executable = crate_info.output if crate_info.type == "bin" or crate_info.is_test or out_binary else None,
+ ),
+ ]
+ if toolchain.target_arch != "wasm32":
+ providers += establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library)
+ if pdb_file:
+ providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
+ if dsym_folder:
+ providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
+
+ return providers
+
+def _is_dylib(dep):
+ return not bool(dep.static_library or dep.pic_static_library)
+
+def establish_cc_info(ctx, attr, crate_info, toolchain, cc_toolchain, feature_configuration, interface_library):
+ """If the produced crate is suitable yield a CcInfo to allow for interop with cc rules
+
+ Args:
+ ctx (ctx): The rule's context object
+ attr (struct): Attributes to use in gathering CcInfo
+ crate_info (CrateInfo): The CrateInfo provider of the target crate
+ toolchain (rust_toolchain): The current `rust_toolchain`
+ cc_toolchain (CcToolchainInfo): The current `CcToolchainInfo`
+ feature_configuration (FeatureConfiguration): Feature configuration to be queried.
+ interface_library (File): Optional interface library for cdylib crates on Windows.
+
+ Returns:
+ list: A list containing the CcInfo provider
+ """
+
+ # A test will not need to produce CcInfo as nothing can depend on test targets
+ if crate_info.is_test:
+ return []
+
+ # Only generate CcInfo for particular crate types
+ if crate_info.type not in ("staticlib", "cdylib", "rlib", "lib"):
+ return []
+
+ # TODO: Remove after some resolution to
+ # https://github.com/bazelbuild/rules_rust/issues/771
+ if getattr(attr, "out_binary", False):
+ return []
+
+ if crate_info.type == "staticlib":
+ library_to_link = cc_common.create_library_to_link(
+ actions = ctx.actions,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ static_library = crate_info.output,
+ # TODO(hlopko): handle PIC/NOPIC correctly
+ pic_static_library = crate_info.output,
+ )
+ elif crate_info.type in ("rlib", "lib"):
+ # bazel hard-codes a check for endswith((".a", ".pic.a",
+ # ".lib")) in create_library_to_link, so we work around that
+ # by creating a symlink to the .rlib with a .a extension.
+ dot_a = make_static_lib_symlink(ctx.actions, crate_info.output)
+
+ # TODO(hlopko): handle PIC/NOPIC correctly
+ library_to_link = cc_common.create_library_to_link(
+ actions = ctx.actions,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ static_library = dot_a,
+ # TODO(hlopko): handle PIC/NOPIC correctly
+ pic_static_library = dot_a,
+ )
+ elif crate_info.type == "cdylib":
+ library_to_link = cc_common.create_library_to_link(
+ actions = ctx.actions,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ dynamic_library = crate_info.output,
+ interface_library = interface_library,
+ )
+ else:
+ fail("Unexpected case")
+
+ link_input = cc_common.create_linker_input(
+ owner = ctx.label,
+ libraries = depset([library_to_link]),
+ )
+
+ linking_context = cc_common.create_linking_context(
+ # TODO - What to do for no_std?
+ linker_inputs = depset([link_input]),
+ )
+
+ cc_infos = [
+ CcInfo(linking_context = linking_context),
+ toolchain.stdlib_linkflags,
+ ]
+ for dep in getattr(attr, "deps", []):
+ if CcInfo in dep:
+ cc_infos.append(dep[CcInfo])
+
+ if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
+ # TODO: if we already have an rlib in our deps, we could skip this
+ cc_infos.append(toolchain.libstd_and_allocator_ccinfo)
+
+ return [cc_common.merge_cc_infos(cc_infos = cc_infos)]
+
+def add_edition_flags(args, crate):
+ """Adds the Rust edition flag to an arguments object reference
+
+ Args:
+ args (Args): A reference to an Args object
+ crate (CrateInfo): A CrateInfo provider
+ """
+ if crate.edition != "2015":
+ args.add("--edition={}".format(crate.edition))
+
+def _create_extra_input_args(build_info, dep_info):
+ """Gather additional input arguments from transitive dependencies
+
+ Args:
+ build_info (BuildInfo): The BuildInfo provider from the target Crate's set of inputs.
+ dep_info (DepInfo): The Depinfo provider form the target Crate's set of inputs.
+
+ Returns:
+ tuple: A tuple of the following items:
+ - (depset[File]): A list of all build info `OUT_DIR` File objects
+ - (str): The `OUT_DIR` of the current build info
+ - (File): An optional generated environment file from a `cargo_build_script` target
+ - (depset[File]): All direct and transitive build flag files from the current build info.
+ """
+ input_files = []
+
+ # Arguments to the commandline line wrapper that are going to be used
+ # to create the final command line
+ out_dir = None
+ build_env_file = None
+ build_flags_files = []
+
+ if build_info:
+ out_dir = build_info.out_dir.path
+ build_env_file = build_info.rustc_env
+ build_flags_files.append(build_info.flags)
+ build_flags_files.append(build_info.link_flags)
+ input_files.append(build_info.out_dir)
+ input_files.append(build_info.link_flags)
+
+ return (
+ depset(input_files, transitive = [dep_info.link_search_path_files]),
+ out_dir,
+ build_env_file,
+ depset(build_flags_files, transitive = [dep_info.link_search_path_files]),
+ )
+
+def _compute_rpaths(toolchain, output_dir, dep_info, use_pic):
+ """Determine the artifact's rpaths relative to the bazel root for runtime linking of shared libraries.
+
+ Args:
+ toolchain (rust_toolchain): The current `rust_toolchain`
+ output_dir (str): The output directory of the current target
+ dep_info (DepInfo): The current target's dependency info
+ use_pic: If set, prefers pic_static_library over static_library.
+
+ Returns:
+ depset: A set of relative paths from the output directory to each dependency
+ """
+
+ # Windows has no rpath equivalent, so always return an empty depset.
+ if toolchain.os == "windows":
+ return depset([])
+
+ dylibs = [
+ get_preferred_artifact(lib, use_pic)
+ for linker_input in dep_info.transitive_noncrates.to_list()
+ for lib in linker_input.libraries
+ if _is_dylib(lib)
+ ]
+ if not dylibs:
+ return depset([])
+
+ # For darwin, dylibs compiled by Bazel will fail to be resolved at runtime
+ # without a version of Bazel that includes
+ # https://github.com/bazelbuild/bazel/pull/13427. This is known to not be
+ # included in Bazel 4.1 and below.
+ if toolchain.os != "linux" and toolchain.os != "darwin":
+ fail("Runtime linking is not supported on {}, but found {}".format(
+ toolchain.os,
+ dep_info.transitive_noncrates,
+ ))
+
+ # Multiple dylibs can be present in the same directory, so deduplicate them.
+ return depset([
+ relativize(lib_dir, output_dir)
+ for lib_dir in _get_dir_names(dylibs)
+ ])
+
+def _get_dir_names(files):
+ """Returns a list of directory names from the given list of File objects
+
+ Args:
+ files (list): A list of File objects
+
+ Returns:
+ list: A list of directory names for all files
+ """
+ dirs = {}
+ for f in files:
+ dirs[f.dirname] = None
+ return dirs.keys()
+
+def add_crate_link_flags(args, dep_info, force_all_deps_direct = False):
+ """Adds link flags to an Args object reference
+
+ Args:
+ args (Args): An arguments object reference
+ dep_info (DepInfo): The current target's dependency info
+ force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
+ to the commandline as opposed to -L.
+ """
+
+ if force_all_deps_direct:
+ args.add_all(
+ depset(
+ transitive = [
+ dep_info.direct_crates,
+ dep_info.transitive_crates,
+ ],
+ ),
+ uniquify = True,
+ map_each = _crate_to_link_flag,
+ )
+ else:
+ # nb. Direct crates are linked via --extern regardless of their crate_type
+ args.add_all(dep_info.direct_crates, map_each = _crate_to_link_flag)
+ args.add_all(
+ dep_info.transitive_crates,
+ map_each = _get_crate_dirname,
+ uniquify = True,
+ format_each = "-Ldependency=%s",
+ )
+
+def _crate_to_link_flag(crate):
+ """A helper macro used by `add_crate_link_flags` for adding crate link flags to a Arg object
+
+ Args:
+ crate (CrateInfo|AliasableDepInfo): A CrateInfo or an AliasableDepInfo provider
+
+ Returns:
+ list: Link flags for the given provider
+ """
+
+ # This is AliasableDepInfo, we should use the alias as a crate name
+ if hasattr(crate, "dep"):
+ name = crate.name
+ crate_info = crate.dep
+ else:
+ name = crate.name
+ crate_info = crate
+ return ["--extern={}={}".format(name, crate_info.output.path)]
+
+def _get_crate_dirname(crate):
+ """A helper macro used by `add_crate_link_flags` for getting the directory name of the current crate's output path
+
+ Args:
+ crate (CrateInfo): A CrateInfo provider from the current rule
+
+ Returns:
+ str: The directory name of the the output File that will be produced.
+ """
+ return crate.output.dirname
+
+def _portable_link_flags(lib, use_pic, ambiguous_libs):
+ artifact = get_preferred_artifact(lib, use_pic)
+ if ambiguous_libs and artifact.path in ambiguous_libs:
+ artifact = ambiguous_libs[artifact.path]
+ if lib.static_library or lib.pic_static_library:
+ return [
+ "-lstatic=%s" % get_lib_name(artifact),
+ ]
+ elif _is_dylib(lib):
+ return [
+ "-ldylib=%s" % get_lib_name(artifact),
+ ]
+
+ return []
+
+def _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs):
+ linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
+ ret = []
+ for lib in linker_input.libraries:
+ if lib.alwayslink:
+ ret.extend(["-C", "link-arg=/WHOLEARCHIVE:%s" % get_preferred_artifact(lib, use_pic).path])
+ else:
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+ return ret
+
+def _make_link_flags_darwin(linker_input_and_use_pic_and_ambiguous_libs):
+ linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
+ ret = []
+ for lib in linker_input.libraries:
+ if lib.alwayslink:
+ ret.extend([
+ "-C",
+ ("link-arg=-Wl,-force_load,%s" % get_preferred_artifact(lib, use_pic).path),
+ ])
+ else:
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+ return ret
+
+def _make_link_flags_default(linker_input_and_use_pic_and_ambiguous_libs):
+ linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
+ ret = []
+ for lib in linker_input.libraries:
+ if lib.alwayslink:
+ ret.extend([
+ "-C",
+ "link-arg=-Wl,--whole-archive",
+ "-C",
+ ("link-arg=%s" % get_preferred_artifact(lib, use_pic).path),
+ "-C",
+ "link-arg=-Wl,--no-whole-archive",
+ ])
+ else:
+ ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs))
+ return ret
+
+def _libraries_dirnames(linker_input_and_use_pic_and_ambiguous_libs):
+ link_input, use_pic, _ = linker_input_and_use_pic_and_ambiguous_libs
+
+ # De-duplicate names.
+ return depset([get_preferred_artifact(lib, use_pic).dirname for lib in link_input.libraries]).to_list()
+
+def _add_native_link_flags(args, dep_info, linkstamp_outs, ambiguous_libs, crate_type, toolchain, cc_toolchain, feature_configuration):
+ """Adds linker flags for all dependencies of the current target.
+
+ Args:
+ args (Args): The Args struct for a ctx.action
+ dep_info (DepInfo): Dependency Info provider
+ linkstamp_outs (list): Linkstamp outputs of native dependencies
+ ambiguous_libs (dict): Ambiguous libs, see `_disambiguate_libs`
+ crate_type: Crate type of the current target
+ toolchain (rust_toolchain): The current `rust_toolchain`
+ cc_toolchain (CcToolchainInfo): The current `cc_toolchain`
+ feature_configuration (FeatureConfiguration): feature configuration to use with cc_toolchain
+
+ """
+ if crate_type in ["lib", "rlib"]:
+ return
+
+ use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_type)
+
+ if toolchain.os == "windows":
+ make_link_flags = _make_link_flags_windows
+ elif toolchain.os.startswith("mac") or toolchain.os.startswith("darwin"):
+ make_link_flags = _make_link_flags_darwin
+ else:
+ make_link_flags = _make_link_flags_default
+
+ # TODO(hlopko): Remove depset flattening by using lambdas once we are on >=Bazel 5.0
+ args_and_pic_and_ambiguous_libs = [(arg, use_pic, ambiguous_libs) for arg in dep_info.transitive_noncrates.to_list()]
+ args.add_all(args_and_pic_and_ambiguous_libs, map_each = _libraries_dirnames, uniquify = True, format_each = "-Lnative=%s")
+ if ambiguous_libs:
+ # If there are ambiguous libs, the disambiguation symlinks to them are
+ # all created in the same directory. Add it to the library search path.
+ ambiguous_libs_dirname = ambiguous_libs.values()[0].dirname
+ args.add("-Lnative={}".format(ambiguous_libs_dirname))
+
+ args.add_all(args_and_pic_and_ambiguous_libs, map_each = make_link_flags)
+
+ for linkstamp_out in linkstamp_outs:
+ args.add_all(["-C", "link-arg=%s" % linkstamp_out.path])
+
+ if crate_type in ["dylib", "cdylib"]:
+ # For shared libraries we want to link C++ runtime library dynamically
+ # (for example libstdc++.so or libc++.so).
+ args.add_all(
+ cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
+ map_each = _get_dirname,
+ format_each = "-Lnative=%s",
+ )
+ args.add_all(
+ cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
+ map_each = get_lib_name,
+ format_each = "-ldylib=%s",
+ )
+ else:
+ # For all other crate types we want to link C++ runtime library statically
+ # (for example libstdc++.a or libc++.a).
+ args.add_all(
+ cc_toolchain.static_runtime_lib(feature_configuration = feature_configuration),
+ map_each = _get_dirname,
+ format_each = "-Lnative=%s",
+ )
+ args.add_all(
+ cc_toolchain.static_runtime_lib(feature_configuration = feature_configuration),
+ map_each = get_lib_name,
+ format_each = "-lstatic=%s",
+ )
+
+def _get_dirname(file):
+ """A helper function for `_add_native_link_flags`.
+
+ Args:
+ file (File): The target file
+
+ Returns:
+ str: Directory name of `file`
+ """
+ return file.dirname
+
+def _error_format_impl(ctx):
+ """Implementation of the `error_format` rule
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list containing the ErrorFormatInfo provider
+ """
+ raw = ctx.build_setting_value
+ if raw not in _error_format_values:
+ fail("{} expected a value in `{}` but got `{}`".format(
+ ctx.label,
+ _error_format_values,
+ raw,
+ ))
+ return [ErrorFormatInfo(error_format = raw)]
+
+error_format = rule(
+ doc = (
+ "Change the [--error-format](https://doc.rust-lang.org/rustc/command-line-arguments.html#option-error-format) " +
+ "flag from the command line with `--@rules_rust//:error_format`. See rustc documentation for valid values."
+ ),
+ implementation = _error_format_impl,
+ build_setting = config.string(flag = True),
+)
+
+def _extra_rustc_flags_impl(ctx):
+ return ExtraRustcFlagsInfo(extra_rustc_flags = ctx.build_setting_value)
+
+extra_rustc_flags = rule(
+ doc = (
+ "Add additional rustc_flags from the command line with `--@rules_rust//:extra_rustc_flags`. " +
+ "This flag should only be used for flags that need to be applied across the entire build. For options that " +
+ "apply to individual crates, use the rustc_flags attribute on the individual crate's rule instead. NOTE: " +
+ "These flags not applied to the exec configuration (proc-macros, cargo_build_script, etc); " +
+ "use `--@rules_rust//:extra_exec_rustc_flags` to apply flags to the exec configuration."
+ ),
+ implementation = _extra_rustc_flags_impl,
+ build_setting = config.string_list(flag = True),
+)
+
+def _extra_exec_rustc_flags_impl(ctx):
+ return ExtraExecRustcFlagsInfo(extra_exec_rustc_flags = ctx.build_setting_value)
+
+extra_exec_rustc_flags = rule(
+ doc = (
+ "Add additional rustc_flags in the exec configuration from the command line with `--@rules_rust//:extra_exec_rustc_flags`. " +
+ "This flag should only be used for flags that need to be applied across the entire build. " +
+ "These flags only apply to the exec configuration (proc-macros, cargo_build_script, etc)."
+ ),
+ implementation = _extra_exec_rustc_flags_impl,
+ build_setting = config.string_list(flag = True),
+)
diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl
new file mode 100644
index 0000000..86c2acd
--- /dev/null
+++ b/rust/private/rustdoc.bzl
@@ -0,0 +1,318 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Rules for generating documentation with `rustdoc` for Bazel built crates"""
+
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:rustc.bzl", "collect_deps", "collect_inputs", "construct_arguments")
+load("//rust/private:utils.bzl", "dedent", "find_cc_toolchain", "find_toolchain")
+
+def _strip_crate_info_output(crate_info):
+ """Set the CrateInfo.output to None for a given CrateInfo provider.
+
+ Args:
+ crate_info (CrateInfo): A provider
+
+ Returns:
+ CrateInfo: A modified CrateInfo provider
+ """
+ return rust_common.create_crate_info(
+ name = crate_info.name,
+ type = crate_info.type,
+ root = crate_info.root,
+ srcs = crate_info.srcs,
+ deps = crate_info.deps,
+ proc_macro_deps = crate_info.proc_macro_deps,
+ aliases = crate_info.aliases,
+ # This crate info should have no output
+ output = None,
+ edition = crate_info.edition,
+ rustc_env = crate_info.rustc_env,
+ is_test = crate_info.is_test,
+ compile_data = crate_info.compile_data,
+ )
+
+def rustdoc_compile_action(
+ ctx,
+ toolchain,
+ crate_info,
+ output = None,
+ rustdoc_flags = [],
+ is_test = False):
+ """Create a struct of information needed for a `rustdoc` compile action based on crate passed to the rustdoc rule.
+
+ Args:
+ ctx (ctx): The rule's context object.
+ toolchain (rust_toolchain): The currently configured `rust_toolchain`.
+ crate_info (CrateInfo): The provider of the crate passed to a rustdoc rule.
+ output (File, optional): An optional output a `rustdoc` action is intended to produce.
+ rustdoc_flags (list, optional): A list of `rustdoc` specific flags.
+ is_test (bool, optional): If True, the action will be configured for `rust_doc_test` targets
+
+ Returns:
+ struct: A struct of some `ctx.actions.run` arguments.
+ """
+
+ # If an output was provided, ensure it's used in rustdoc arguments
+ if output:
+ rustdoc_flags = [
+ "--output",
+ output.path,
+ ] + rustdoc_flags
+
+ cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+
+ dep_info, build_info, linkstamps = collect_deps(
+ deps = crate_info.deps,
+ proc_macro_deps = crate_info.proc_macro_deps,
+ aliases = crate_info.aliases,
+ )
+
+ compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
+ ctx = ctx,
+ file = ctx.file,
+ files = ctx.files,
+ linkstamps = linkstamps,
+ toolchain = toolchain,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ crate_info = crate_info,
+ dep_info = dep_info,
+ build_info = build_info,
+ )
+
+ # Since this crate is not actually producing the output described by the
+ # given CrateInfo, this attribute needs to be stripped to allow the rest
+ # of the rustc functionality in `construct_arguments` to avoid generating
+ # arguments expecting to do so.
+ rustdoc_crate_info = _strip_crate_info_output(crate_info)
+
+ args, env = construct_arguments(
+ ctx = ctx,
+ attr = ctx.attr,
+ file = ctx.file,
+ toolchain = toolchain,
+ tool_path = toolchain.rust_doc.short_path if is_test else toolchain.rust_doc.path,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ crate_info = rustdoc_crate_info,
+ dep_info = dep_info,
+ linkstamp_outs = linkstamp_outs,
+ ambiguous_libs = ambiguous_libs,
+ output_hash = None,
+ rust_flags = rustdoc_flags,
+ out_dir = out_dir,
+ build_env_files = build_env_files,
+ build_flags_files = build_flags_files,
+ emit = [],
+ remap_path_prefix = None,
+ force_link = True,
+ )
+
+ # Because rustdoc tests compile tests outside of the sandbox, the sysroot
+ # must be updated to the `short_path` equivilant as it will now be
+ # a part of runfiles.
+ if is_test:
+ if "SYSROOT" in env:
+ env.update({"SYSROOT": "${{pwd}}/{}".format(toolchain.sysroot_short_path)})
+
+ # `rustdoc` does not support the SYSROOT environment variable. To account
+ # for this, the flag must be explicitly passed to the `rustdoc` binary.
+ args.rustc_flags.add("--sysroot=${{pwd}}/{}".format(toolchain.sysroot_short_path))
+
+ return struct(
+ executable = ctx.executable._process_wrapper,
+ inputs = depset([crate_info.output], transitive = [compile_inputs]),
+ env = env,
+ arguments = args.all,
+ tools = [toolchain.rust_doc],
+ )
+
+def _zip_action(ctx, input_dir, output_zip, crate_label):
+ """Creates an archive of the generated documentation from `rustdoc`
+
+ Args:
+ ctx (ctx): The `rust_doc` rule's context object
+ input_dir (File): A directory containing the outputs from rustdoc
+ output_zip (File): The location of the output archive containing generated documentation
+ crate_label (Label): The label of the crate docs are being generated for.
+ """
+ args = ctx.actions.args()
+ args.add(ctx.executable._zipper)
+ args.add(output_zip)
+ args.add(ctx.bin_dir.path)
+ args.add_all([input_dir], expand_directories = True)
+ ctx.actions.run(
+ executable = ctx.executable._dir_zipper,
+ inputs = [input_dir],
+ outputs = [output_zip],
+ arguments = [args],
+ mnemonic = "RustdocZip",
+ progress_message = "Creating RustdocZip for {}".format(crate_label),
+ tools = [ctx.executable._zipper],
+ )
+
+def _rust_doc_impl(ctx):
+ """The implementation of the `rust_doc` rule
+
+ Args:
+ ctx (ctx): The rule's context object
+ """
+
+ crate = ctx.attr.crate
+ crate_info = crate[rust_common.crate_info]
+
+ output_dir = ctx.actions.declare_directory("{}.rustdoc".format(ctx.label.name))
+
+ # Add the current crate as an extern for the compile action
+ rustdoc_flags = [
+ "--extern",
+ "{}={}".format(crate_info.name, crate_info.output.path),
+ ]
+
+ action = rustdoc_compile_action(
+ ctx = ctx,
+ toolchain = find_toolchain(ctx),
+ crate_info = crate_info,
+ output = output_dir,
+ rustdoc_flags = rustdoc_flags,
+ )
+
+ ctx.actions.run(
+ mnemonic = "Rustdoc",
+ progress_message = "Generating Rustdoc for {}".format(crate.label),
+ outputs = [output_dir],
+ executable = action.executable,
+ inputs = action.inputs,
+ env = action.env,
+ arguments = action.arguments,
+ tools = action.tools,
+ )
+
+ # This rule does nothing without a single-file output, though the directory should've sufficed.
+ _zip_action(ctx, output_dir, ctx.outputs.rust_doc_zip, crate.label)
+
+ return [
+ DefaultInfo(
+ files = depset([ctx.outputs.rust_doc_zip]),
+ ),
+ OutputGroupInfo(
+ rustdoc_dir = depset([output_dir]),
+ rustdoc_zip = depset([ctx.outputs.rust_doc_zip]),
+ ),
+ ]
+
+rust_doc = rule(
+ doc = dedent("""\
+ Generates code documentation.
+
+ Example:
+ Suppose you have the following directory structure for a Rust library crate:
+
+ ```
+ [workspace]/
+ WORKSPACE
+ hello_lib/
+ BUILD
+ src/
+ lib.rs
+ ```
+
+ To build [`rustdoc`][rustdoc] documentation for the `hello_lib` crate, define \
+ a `rust_doc` rule that depends on the the `hello_lib` `rust_library` target:
+
+ [rustdoc]: https://doc.rust-lang.org/book/documentation.html
+
+ ```python
+ package(default_visibility = ["//visibility:public"])
+
+ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_doc")
+
+ rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+ )
+
+ rust_doc(
+ name = "hello_lib_doc",
+ crate = ":hello_lib",
+ )
+ ```
+
+ Running `bazel build //hello_lib:hello_lib_doc` will build a zip file containing \
+ the documentation for the `hello_lib` library crate generated by `rustdoc`.
+ """),
+ implementation = _rust_doc_impl,
+ attrs = {
+ "crate": attr.label(
+ doc = (
+ "The label of the target to generate code documentation for.\n" +
+ "\n" +
+ "`rust_doc` can generate HTML code documentation for the source files of " +
+ "`rust_library` or `rust_binary` targets."
+ ),
+ providers = [rust_common.crate_info],
+ mandatory = True,
+ ),
+ "html_after_content": attr.label(
+ doc = "File to add in `<body>`, after content.",
+ allow_single_file = [".html", ".md"],
+ ),
+ "html_before_content": attr.label(
+ doc = "File to add in `<body>`, before content.",
+ allow_single_file = [".html", ".md"],
+ ),
+ "html_in_header": attr.label(
+ doc = "File to add to `<head>`.",
+ allow_single_file = [".html", ".md"],
+ ),
+ "markdown_css": attr.label_list(
+ doc = "CSS files to include via `<link>` in a rendered Markdown file.",
+ allow_files = [".css"],
+ ),
+ "_cc_toolchain": attr.label(
+ doc = "In order to use find_cpp_toolchain, you must define the '_cc_toolchain' attribute on your rule or aspect.",
+ default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+ ),
+ "_dir_zipper": attr.label(
+ doc = "A tool that orchestrates the creation of zip archives for rustdoc outputs.",
+ default = Label("//util/dir_zipper"),
+ cfg = "exec",
+ executable = True,
+ ),
+ "_process_wrapper": attr.label(
+ doc = "A process wrapper for running rustdoc on all platforms",
+ default = Label("@rules_rust//util/process_wrapper"),
+ executable = True,
+ allow_single_file = True,
+ cfg = "exec",
+ ),
+ "_zipper": attr.label(
+ doc = "A Bazel provided tool for creating archives",
+ default = Label("@bazel_tools//tools/zip:zipper"),
+ cfg = "exec",
+ executable = True,
+ ),
+ },
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ outputs = {
+ "rust_doc_zip": "%{name}.zip",
+ },
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+)
diff --git a/rust/private/rustdoc_test.bzl b/rust/private/rustdoc_test.bzl
new file mode 100644
index 0000000..663c65f
--- /dev/null
+++ b/rust/private/rustdoc_test.bzl
@@ -0,0 +1,225 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Rules for performing `rustdoc --test` on Bazel built crates"""
+
+load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:rustdoc.bzl", "rustdoc_compile_action")
+load("//rust/private:utils.bzl", "dedent", "find_toolchain")
+
+def _construct_writer_arguments(ctx, test_runner, action, crate_info):
+ """Construct arguments and environment variables specific to `rustdoc_test_writer`.
+
+ This is largely solving for the fact that tests run from a runfiles directory
+ where actions run in an execroot. But it also tracks what environment variables
+ were explicitly added to the action.
+
+ Args:
+ ctx (ctx): The rule's context object.
+ test_runner (File): The test_runner output file declared by `rustdoc_test`.
+ action (struct): Action arguments generated by `rustdoc_compile_action`.
+ crate_info (CrateInfo): The provider of the crate who's docs are being tested.
+
+ Returns:
+ tuple: A tuple of `rustdoc_test_writer` specific inputs
+ - Args: Arguments for the test writer
+ - dict: Required environment variables
+ """
+
+ writer_args = ctx.actions.args()
+
+ # Track the output path where the test writer should write the test
+ writer_args.add("--output={}".format(test_runner.path))
+
+ # Track what environment variables should be written to the test runner
+ writer_args.add("--action_env=DEVELOPER_DIR")
+ writer_args.add("--action_env=PATHEXT")
+ writer_args.add("--action_env=SDKROOT")
+ writer_args.add("--action_env=SYSROOT")
+ for var in action.env.keys():
+ writer_args.add("--action_env={}".format(var))
+
+ # Since the test runner will be running from a runfiles directory, the
+ # paths originally generated for the build action will not map to any
+ # files. To ensure rustdoc can find the appropriate dependencies, the
+ # file roots are identified and tracked for each dependency so it can be
+ # stripped from the test runner.
+ for dep in crate_info.deps.to_list():
+ dep_crate_info = getattr(dep, "crate_info", None)
+ dep_dep_info = getattr(dep, "dep_info", None)
+ if dep_crate_info:
+ root = dep_crate_info.output.root.path
+ writer_args.add("--strip_substring={}/".format(root))
+ if dep_dep_info:
+ for direct_dep in dep_dep_info.direct_crates.to_list():
+ root = direct_dep.dep.output.root.path
+ writer_args.add("--strip_substring={}/".format(root))
+ for transitive_dep in dep_dep_info.transitive_crates.to_list():
+ root = transitive_dep.output.root.path
+ writer_args.add("--strip_substring={}/".format(root))
+
+ # Indicate that the rustdoc_test args are over.
+ writer_args.add("--")
+
+ # Prepare for the process runner to ingest the rest of the arguments
+ # to match the expectations of `rustc_compile_action`.
+ writer_args.add(ctx.executable._process_wrapper.short_path)
+
+ return (writer_args, action.env)
+
+def _rust_doc_test_impl(ctx):
+ """The implementation for the `rust_doc_test` rule
+
+ Args:
+ ctx (ctx): The rule's context object
+
+ Returns:
+ list: A list containing a DefaultInfo provider
+ """
+
+ toolchain = find_toolchain(ctx)
+
+ crate = ctx.attr.crate
+ crate_info = crate[rust_common.crate_info]
+
+ if toolchain.os == "windows":
+ test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.bat")
+ else:
+ test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.sh")
+
+ # Add the current crate as an extern for the compile action
+ rustdoc_flags = [
+ "--extern",
+ "{}={}".format(crate_info.name, crate_info.output.short_path),
+ "--test",
+ ]
+
+ action = rustdoc_compile_action(
+ ctx = ctx,
+ toolchain = toolchain,
+ crate_info = crate_info,
+ rustdoc_flags = rustdoc_flags,
+ is_test = True,
+ )
+
+ tools = action.tools + [ctx.executable._process_wrapper]
+
+ writer_args, env = _construct_writer_arguments(
+ ctx = ctx,
+ test_runner = test_runner,
+ action = action,
+ crate_info = crate_info,
+ )
+
+ # Allow writer environment variables to override those from the action.
+ action.env.update(env)
+
+ ctx.actions.run(
+ mnemonic = "RustdocTestWriter",
+ progress_message = "Generating Rustdoc test runner for {}".format(crate.label),
+ executable = ctx.executable._test_writer,
+ inputs = action.inputs,
+ tools = tools,
+ arguments = [writer_args] + action.arguments,
+ env = action.env,
+ outputs = [test_runner],
+ )
+
+ return [DefaultInfo(
+ files = depset([test_runner]),
+ runfiles = ctx.runfiles(files = tools, transitive_files = action.inputs),
+ executable = test_runner,
+ )]
+
+rust_doc_test = rule(
+ implementation = _rust_doc_test_impl,
+ attrs = {
+ "crate": attr.label(
+ doc = (
+ "The label of the target to generate code documentation for. " +
+ "`rust_doc_test` can generate HTML code documentation for the " +
+ "source files of `rust_library` or `rust_binary` targets."
+ ),
+ providers = [rust_common.crate_info],
+ mandatory = True,
+ ),
+ "_cc_toolchain": attr.label(
+ doc = (
+ "In order to use find_cc_toolchain, your rule has to depend " +
+ "on C++ toolchain. See @rules_cc//cc:find_cc_toolchain.bzl " +
+ "docs for details."
+ ),
+ default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+ ),
+ "_process_wrapper": attr.label(
+ doc = "A process wrapper for running rustdoc on all platforms",
+ cfg = "exec",
+ default = Label("//util/process_wrapper"),
+ executable = True,
+ ),
+ "_test_writer": attr.label(
+ doc = "A binary used for writing script for use as the test executable.",
+ cfg = "exec",
+ default = Label("//tools/rustdoc:rustdoc_test_writer"),
+ executable = True,
+ ),
+ },
+ test = True,
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+ doc = dedent("""\
+ Runs Rust documentation tests.
+
+ Example:
+
+ Suppose you have the following directory structure for a Rust library crate:
+
+ ```output
+ [workspace]/
+ WORKSPACE
+ hello_lib/
+ BUILD
+ src/
+ lib.rs
+ ```
+
+ To run [documentation tests][doc-test] for the `hello_lib` crate, define a `rust_doc_test` \
+ target that depends on the `hello_lib` `rust_library` target:
+
+ [doc-test]: https://doc.rust-lang.org/book/documentation.html#documentation-as-tests
+
+ ```python
+ package(default_visibility = ["//visibility:public"])
+
+ load("@rules_rust//rust:defs.bzl", "rust_library", "rust_doc_test")
+
+ rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+ )
+
+ rust_doc_test(
+ name = "hello_lib_doc_test",
+ crate = ":hello_lib",
+ )
+ ```
+
+ Running `bazel test //hello_lib:hello_lib_doc_test` will run all documentation tests for the `hello_lib` library crate.
+ """),
+)
diff --git a/rust/private/rustfmt.bzl b/rust/private/rustfmt.bzl
new file mode 100644
index 0000000..9c59f48
--- /dev/null
+++ b/rust/private/rustfmt.bzl
@@ -0,0 +1,186 @@
+"""A module defining rustfmt rules"""
+
+load(":common.bzl", "rust_common")
+load(":utils.bzl", "find_toolchain")
+
+def _find_rustfmtable_srcs(target, aspect_ctx = None):
+ """Parse a target for rustfmt formattable sources.
+
+ Args:
+ target (Target): The target the aspect is running on.
+ aspect_ctx (ctx, optional): The aspect's context object.
+
+ Returns:
+ list: A list of formattable sources (`File`).
+ """
+ if rust_common.crate_info not in target:
+ return []
+
+ # Ignore external targets
+ if target.label.workspace_root.startswith("external"):
+ return []
+
+ # Targets annotated with `norustfmt` will not be formatted
+ if aspect_ctx and "norustfmt" in aspect_ctx.rule.attr.tags:
+ return []
+
+ crate_info = target[rust_common.crate_info]
+
+ # Filter out any generated files
+ srcs = [src for src in crate_info.srcs.to_list() if src.is_source]
+
+ return srcs
+
+def _generate_manifest(edition, srcs, ctx):
+ # Gather the source paths to non-generated files
+ src_paths = [src.path for src in srcs]
+
+ # Write the rustfmt manifest
+ manifest = ctx.actions.declare_file(ctx.label.name + ".rustfmt")
+ ctx.actions.write(
+ output = manifest,
+ content = "\n".join(src_paths + [
+ edition,
+ ]),
+ )
+
+ return manifest
+
+def _perform_check(edition, srcs, ctx):
+ toolchain = find_toolchain(ctx)
+ config = ctx.file._config
+ marker = ctx.actions.declare_file(ctx.label.name + ".rustfmt.ok")
+
+ args = ctx.actions.args()
+ args.add("--touch-file")
+ args.add(marker)
+ args.add("--")
+ args.add(toolchain.rustfmt)
+ args.add("--config-path")
+ args.add(config)
+ args.add("--edition")
+ args.add(edition)
+ args.add("--check")
+ args.add_all(srcs)
+
+ ctx.actions.run(
+ executable = ctx.executable._process_wrapper,
+ inputs = srcs + [config],
+ outputs = [marker],
+ tools = [toolchain.rustfmt],
+ arguments = [args],
+ mnemonic = "Rustfmt",
+ )
+
+ return marker
+
+def _rustfmt_aspect_impl(target, ctx):
+ srcs = _find_rustfmtable_srcs(target, ctx)
+
+ # If there are no formattable sources, do nothing.
+ if not srcs:
+ return []
+
+ # Parse the edition to use for formatting from the target
+ edition = target[rust_common.crate_info].edition
+
+ manifest = _generate_manifest(edition, srcs, ctx)
+ marker = _perform_check(edition, srcs, ctx)
+
+ return [
+ OutputGroupInfo(
+ rustfmt_manifest = depset([manifest]),
+ rustfmt_checks = depset([marker]),
+ ),
+ ]
+
+rustfmt_aspect = aspect(
+ implementation = _rustfmt_aspect_impl,
+ doc = """\
+This aspect is used to gather information about a crate for use in rustfmt and perform rustfmt checks
+
+Output Groups:
+
+- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings.
+- `rustfmt_checks`: Executes `rustfmt --check` on the specified target.
+
+The build setting `@rules_rust//:rustfmt.toml` is used to control the Rustfmt [configuration settings][cs]
+used at runtime.
+
+[cs]: https://rust-lang.github.io/rustfmt/
+
+This aspect is executed on any target which provides the `CrateInfo` provider. However
+users may tag a target with `norustfmt` to have it skipped. Additionally, generated
+source files are also ignored by this aspect.
+""",
+ attrs = {
+ "_config": attr.label(
+ doc = "The `rustfmt.toml` file used for formatting",
+ allow_single_file = True,
+ default = Label("//:rustfmt.toml"),
+ ),
+ "_process_wrapper": attr.label(
+ doc = "A process wrapper for running rustfmt on all platforms",
+ cfg = "exec",
+ executable = True,
+ default = Label("//util/process_wrapper"),
+ ),
+ },
+ incompatible_use_toolchain_transition = True,
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ ],
+)
+
+def _rustfmt_test_impl(ctx):
+ # The executable of a test target must be the output of an action in
+ # the rule implementation. This file is simply a symlink to the real
+ # rustfmt test runner.
+ runner = ctx.actions.declare_file("{}{}".format(
+ ctx.label.name,
+ ctx.executable._runner.extension,
+ ))
+
+ ctx.actions.symlink(
+ output = runner,
+ target_file = ctx.executable._runner,
+ is_executable = True,
+ )
+
+ manifests = [target[OutputGroupInfo].rustfmt_manifest for target in ctx.attr.targets]
+ srcs = [depset(_find_rustfmtable_srcs(target)) for target in ctx.attr.targets]
+
+ runfiles = ctx.runfiles(
+ transitive_files = depset(transitive = manifests + srcs),
+ )
+
+ runfiles = runfiles.merge(
+ ctx.attr._runner[DefaultInfo].default_runfiles,
+ )
+
+ return [DefaultInfo(
+ files = depset([runner]),
+ runfiles = runfiles,
+ executable = runner,
+ )]
+
+rustfmt_test = rule(
+ implementation = _rustfmt_test_impl,
+ doc = "A test rule for performing `rustfmt --check` on a set of targets",
+ attrs = {
+ "targets": attr.label_list(
+ doc = "Rust targets to run `rustfmt --check` on.",
+ providers = [rust_common.crate_info],
+ aspects = [rustfmt_aspect],
+ ),
+ "_runner": attr.label(
+ doc = "The rustfmt test runner",
+ cfg = "exec",
+ executable = True,
+ default = Label("//tools/rustfmt:rustfmt_test"),
+ ),
+ },
+ test = True,
+)
diff --git a/rust/private/stamp.bzl b/rust/private/stamp.bzl
new file mode 100644
index 0000000..1a7cab6
--- /dev/null
+++ b/rust/private/stamp.bzl
@@ -0,0 +1,75 @@
+"""A small utility module dedicated to detecting whether or not the `--stamp` flag is enabled
+
+This module can be removed likely after the following PRs ar addressed:
+- https://github.com/bazelbuild/bazel/issues/11164
+"""
+
+load("//rust/private:utils.bzl", "dedent")
+
+StampSettingInfo = provider(
+ doc = "Information about the `--stamp` command line flag",
+ fields = {
+ "value": "bool: Whether or not the `--stamp` flag was enabled",
+ },
+)
+
+def _stamp_build_setting_impl(ctx):
+ return StampSettingInfo(value = ctx.attr.value)
+
+_stamp_build_setting = rule(
+ doc = dedent("""\
+ Whether to encode build information into the binary. Possible values:
+
+ - stamp = 1: Always stamp the build information into the binary, even in [--nostamp][stamp] builds. \
+ This setting should be avoided, since it potentially kills remote caching for the binary and \
+ any downstream actions that depend on it.
+ - stamp = 0: Always replace build information by constant values. This gives good build result caching.
+ - stamp = -1: Embedding of build information is controlled by the [--[no]stamp][stamp] flag.
+
+ Stamped binaries are not rebuilt unless their dependencies change.
+ [stamp]: https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
+ """),
+ implementation = _stamp_build_setting_impl,
+ attrs = {
+ "value": attr.bool(
+ doc = "The default value of the stamp build flag",
+ mandatory = True,
+ ),
+ },
+)
+
+def stamp_build_setting(name, visibility = ["//visibility:public"]):
+ native.config_setting(
+ name = "stamp_detect",
+ values = {"stamp": "1"},
+ visibility = visibility,
+ )
+
+ _stamp_build_setting(
+ name = name,
+ value = select({
+ ":stamp_detect": True,
+ "//conditions:default": False,
+ }),
+ visibility = visibility,
+ )
+
+def is_stamping_enabled(attr):
+ """Determine whether or not build stamping is enabled
+
+ Args:
+ attr (struct): A rule's struct of attributes (`ctx.attr`)
+
+ Returns:
+ bool: The stamp value
+ """
+ stamp_num = getattr(attr, "stamp", -1)
+ if stamp_num == 1:
+ return True
+ elif stamp_num == 0:
+ return False
+ elif stamp_num == -1:
+ stamp_flag = getattr(attr, "_stamp_flag", None)
+ return stamp_flag[StampSettingInfo].value if stamp_flag else False
+ else:
+ fail("Unexpected `stamp` value: {}".format(stamp_num))
diff --git a/rust/private/toolchain_utils.bzl b/rust/private/toolchain_utils.bzl
new file mode 100644
index 0000000..fcd4fb7
--- /dev/null
+++ b/rust/private/toolchain_utils.bzl
@@ -0,0 +1,82 @@
+"""A module defining toolchain utilities"""
+
+def _toolchain_files_impl(ctx):
+ toolchain = ctx.toolchains[str(Label("//rust:toolchain"))]
+
+ runfiles = None
+ if ctx.attr.tool == "cargo":
+ files = depset([toolchain.cargo])
+ runfiles = ctx.runfiles(
+ files = [
+ toolchain.cargo,
+ toolchain.rustc,
+ ],
+ transitive_files = toolchain.rustc_lib,
+ )
+ elif ctx.attr.tool == "clippy":
+ files = depset([toolchain.clippy_driver])
+ runfiles = ctx.runfiles(
+ files = [
+ toolchain.clippy_driver,
+ toolchain.rustc,
+ ],
+ transitive_files = toolchain.rustc_lib,
+ )
+ elif ctx.attr.tool == "rustc":
+ files = depset([toolchain.rustc])
+ runfiles = ctx.runfiles(
+ files = [toolchain.rustc],
+ transitive_files = toolchain.rustc_lib,
+ )
+ elif ctx.attr.tool == "rustdoc":
+ files = depset([toolchain.rust_doc])
+ runfiles = ctx.runfiles(
+ files = [toolchain.rust_doc],
+ transitive_files = toolchain.rustc_lib,
+ )
+ elif ctx.attr.tool == "rustfmt":
+ files = depset([toolchain.rustfmt])
+ runfiles = ctx.runfiles(
+ files = [toolchain.rustfmt],
+ transitive_files = toolchain.rustc_lib,
+ )
+ elif ctx.attr.tool == "rustc_lib":
+ files = toolchain.rustc_lib
+ elif ctx.attr.tool == "rustc_srcs":
+ files = toolchain.rustc_srcs.files
+ elif ctx.attr.tool == "rust_std" or ctx.attr.tool == "rust_stdlib" or ctx.attr.tool == "rust_lib":
+ files = toolchain.rust_std
+ else:
+ fail("Unsupported tool: ", ctx.attr.tool)
+
+ return [DefaultInfo(
+ files = files,
+ runfiles = runfiles,
+ )]
+
+toolchain_files = rule(
+ doc = "A rule for fetching files from a rust toolchain.",
+ implementation = _toolchain_files_impl,
+ attrs = {
+ "tool": attr.string(
+ doc = "The desired tool to get form the current rust_toolchain",
+ values = [
+ "cargo",
+ "clippy",
+ "rust_lib",
+ "rust_std",
+ "rust_stdlib",
+ "rustc_lib",
+ "rustc_srcs",
+ "rustc",
+ "rustdoc",
+ "rustfmt",
+ ],
+ mandatory = True,
+ ),
+ },
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ ],
+ incompatible_use_toolchain_transition = True,
+)
diff --git a/rust/private/transitions.bzl b/rust/private/transitions.bzl
new file mode 100644
index 0000000..cd28efd
--- /dev/null
+++ b/rust/private/transitions.bzl
@@ -0,0 +1,111 @@
+# buildifier: disable=module-docstring
+load("//rust:defs.bzl", "rust_common")
+
+def _wasm_bindgen_transition(_settings, _attr):
+ """The implementation of the `wasm_bindgen_transition` transition
+
+ Args:
+ _settings (dict): A dict {String:Object} of all settings declared
+ in the inputs parameter to `transition()`
+ _attr (dict): A dict of attributes and values of the rule to which
+ the transition is attached
+
+ Returns:
+ dict: A dict of new build settings values to apply
+ """
+ return {"//command_line_option:platforms": str(Label("//rust/platform:wasm"))}
+
+wasm_bindgen_transition = transition(
+ implementation = _wasm_bindgen_transition,
+ inputs = [],
+ outputs = ["//command_line_option:platforms"],
+)
+
+def _import_macro_dep_bootstrap_transition(_settings, _attr):
+ """The implementation of the `import_macro_dep_bootstrap_transition` transition.
+
+ This transition modifies the config to start using the fake macro
+ implementation, so that the macro itself can be bootstrapped without
+ creating a dependency cycle, even while every Rust target has an implicit
+ dependency on the "import" macro (either real or fake).
+
+ Args:
+ _settings (dict): a dict {String:Object} of all settings declared in the
+ inputs parameter to `transition()`.
+ _attr (dict): A dict of attributes and values of the rule to which the
+ transition is attached.
+
+ Returns:
+ dict: A dict of new build settings values to apply.
+ """
+ return {"@rules_rust//rust/settings:use_real_import_macro": False}
+
+import_macro_dep_bootstrap_transition = transition(
+ implementation = _import_macro_dep_bootstrap_transition,
+ inputs = [],
+ outputs = ["@rules_rust//rust/settings:use_real_import_macro"],
+)
+
+def _with_import_macro_bootstrapping_mode_impl(ctx):
+ target = ctx.attr.target[0]
+ return [target[rust_common.crate_info], target[rust_common.dep_info]]
+
+with_import_macro_bootstrapping_mode = rule(
+ implementation = _with_import_macro_bootstrapping_mode_impl,
+ attrs = {
+ "target": attr.label(
+ cfg = import_macro_dep_bootstrap_transition,
+ allow_single_file = True,
+ mandatory = True,
+ executable = False,
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = Label("//tools/allowlists/function_transition_allowlist"),
+ ),
+ },
+)
+
+def _without_process_wrapper_transition_impl(_settings, _attr):
+ """This transition allows rust_* rules to invoke rustc without process_wrapper."""
+ return {
+ "//rust/settings:use_process_wrapper": False,
+ }
+
+without_process_wrapper_transition = transition(
+ implementation = _without_process_wrapper_transition_impl,
+ inputs = [],
+ outputs = ["//rust/settings:use_process_wrapper"],
+)
+
+def _without_process_wrapper_impl(ctx):
+ executable = ctx.executable.target
+ link_name = ctx.label.name
+
+ # Append .exe if on windows
+ if executable.extension:
+ link_name = link_name + "." + executable.extension
+ link = ctx.actions.declare_file(link_name)
+ ctx.actions.symlink(
+ output = link,
+ target_file = executable,
+ )
+ return [
+ DefaultInfo(
+ executable = link,
+ ),
+ ]
+
+without_process_wrapper = rule(
+ implementation = _without_process_wrapper_impl,
+ attrs = {
+ "target": attr.label(
+ cfg = without_process_wrapper_transition,
+ allow_single_file = True,
+ mandatory = True,
+ executable = True,
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = Label("//tools/allowlists/function_transition_allowlist"),
+ ),
+ },
+)
diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl
new file mode 100644
index 0000000..fa7318b
--- /dev/null
+++ b/rust/private/utils.bzl
@@ -0,0 +1,576 @@
+# Copyright 2015 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility functions not specific to the rust toolchain."""
+
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain")
+load(":providers.bzl", "BuildInfo", "CrateInfo", "DepInfo", "DepVariantInfo")
+
+def find_toolchain(ctx):
+ """Finds the first rust toolchain that is configured.
+
+ Args:
+ ctx (ctx): The ctx object for the current target.
+
+ Returns:
+ rust_toolchain: A Rust toolchain context.
+ """
+ return ctx.toolchains[Label("//rust:toolchain")]
+
+def find_cc_toolchain(ctx):
+ """Extracts a CcToolchain from the current target's context
+
+ Args:
+ ctx (ctx): The current target's rule context object
+
+ Returns:
+ tuple: A tuple of (CcToolchain, FeatureConfiguration)
+ """
+ cc_toolchain = find_rules_cc_toolchain(ctx)
+
+ feature_configuration = cc_common.configure_features(
+ ctx = ctx,
+ cc_toolchain = cc_toolchain,
+ requested_features = ctx.features,
+ unsupported_features = ctx.disabled_features,
+ )
+ return cc_toolchain, feature_configuration
+
+# TODO: Replace with bazel-skylib's `path.dirname`. This requires addressing some
+# dependency issues or generating docs will break.
+def relativize(path, start):
+ """Returns the relative path from start to path.
+
+ Args:
+ path (str): The path to relativize.
+ start (str): The ancestor path against which to relativize.
+
+ Returns:
+ str: The portion of `path` that is relative to `start`.
+ """
+ src_parts = _path_parts(start)
+ dest_parts = _path_parts(path)
+ n = 0
+ for src_part, dest_part in zip(src_parts, dest_parts):
+ if src_part != dest_part:
+ break
+ n += 1
+
+ relative_path = ""
+ for _ in range(n, len(src_parts)):
+ relative_path += "../"
+ relative_path += "/".join(dest_parts[n:])
+
+ return relative_path
+
+def _path_parts(path):
+ """Takes a path and returns a list of its parts with all "." elements removed.
+
+ The main use case of this function is if one of the inputs to relativize()
+ is a relative path, such as "./foo".
+
+ Args:
+ path (str): A string representing a unix path
+
+ Returns:
+ list: A list containing the path parts with all "." elements removed.
+ """
+ path_parts = path.split("/")
+ return [part for part in path_parts if part != "."]
+
+def get_lib_name(lib):
+ """Returns the name of a library artifact, eg. libabc.a -> abc
+
+ Args:
+ lib (File): A library file
+
+ Returns:
+ str: The name of the library
+ """
+ # On macos and windows, dynamic/static libraries always end with the
+ # extension and potential versions will be before the extension, and should
+ # be part of the library name.
+ # On linux, the version usually comes after the extension.
+ # So regardless of the platform we want to find the extension and make
+ # everything left to it the library name.
+
+ # Search for the extension - starting from the right - by removing any
+ # trailing digit.
+ comps = lib.basename.split(".")
+ for comp in reversed(comps):
+ if comp.isdigit():
+ comps.pop()
+ else:
+ break
+
+ # The library name is now everything minus the extension.
+ libname = ".".join(comps[:-1])
+
+ if libname.startswith("lib"):
+ return libname[3:]
+ else:
+ return libname
+
+def abs(value):
+ """Returns the absolute value of a number.
+
+ Args:
+ value (int): A number.
+
+ Returns:
+ int: The absolute value of the number.
+ """
+ if value < 0:
+ return -value
+ return value
+
+def determine_output_hash(crate_root, label):
+ """Generates a hash of the crate root file's path.
+
+ Args:
+ crate_root (File): The crate's root file (typically `lib.rs`).
+ label (Label): The label of the target.
+
+ Returns:
+ str: A string representation of the hash.
+ """
+
+ # Take the absolute value of hash() since it could be negative.
+ h = abs(hash(crate_root.path) + hash(repr(label)))
+ return repr(h)
+
+def get_preferred_artifact(library_to_link, use_pic):
+ """Get the first available library to link from a LibraryToLink object.
+
+ Args:
+ library_to_link (LibraryToLink): See the followg links for additional details:
+ https://docs.bazel.build/versions/master/skylark/lib/LibraryToLink.html
+ use_pic: If set, prefers pic_static_library over static_library.
+
+ Returns:
+ File: Returns the first valid library type (only one is expected)
+ """
+ if use_pic:
+ return (
+ library_to_link.pic_static_library or
+ library_to_link.interface_library or
+ library_to_link.dynamic_library
+ )
+ else:
+ return (
+ library_to_link.static_library or
+ library_to_link.pic_static_library or
+ library_to_link.interface_library or
+ library_to_link.dynamic_library
+ )
+
+def _expand_location(ctx, env, data):
+ """A trivial helper for `_expand_locations`
+
+ Args:
+ ctx (ctx): The rule's context object
+ env (str): The value possibly containing location macros to expand.
+ data (sequence of Targets): see `_expand_locations`
+
+ Returns:
+ string: The location-macro expanded version of the string.
+ """
+ for directive in ("$(execpath ", "$(location "):
+ if directive in env:
+ # build script runner will expand pwd to execroot for us
+ env = env.replace(directive, "${pwd}/" + directive)
+ return ctx.expand_location(env, data)
+
+def expand_dict_value_locations(ctx, env, data):
+ """Performs location-macro expansion on string values.
+
+ $(execroot ...) and $(location ...) are prefixed with ${pwd},
+ which process_wrapper and build_script_runner will expand at run time
+ to the absolute path. This is necessary because include_str!() is relative
+ to the currently compiled file, and build scripts run relative to the
+ manifest dir, so we can not use execroot-relative paths.
+
+ $(rootpath ...) is unmodified, and is useful for passing in paths via
+ rustc_env that are encoded in the binary with env!(), but utilized at
+ runtime, such as in tests. The absolute paths are not usable in this case,
+ as compilation happens in a separate sandbox folder, so when it comes time
+ to read the file at runtime, the path is no longer valid.
+
+ See [`expand_location`](https://docs.bazel.build/versions/main/skylark/lib/ctx.html#expand_location) for detailed documentation.
+
+ Args:
+ ctx (ctx): The rule's context object
+ env (dict): A dict whose values we iterate over
+ data (sequence of Targets): The targets which may be referenced by
+ location macros. This is expected to be the `data` attribute of
+ the target, though may have other targets or attributes mixed in.
+
+ Returns:
+ dict: A dict of environment variables with expanded location macros
+ """
+ return dict([(k, _expand_location(ctx, v, data)) for (k, v) in env.items()])
+
+def expand_list_element_locations(ctx, args, data):
+ """Performs location-macro expansion on a list of string values.
+
+ $(execroot ...) and $(location ...) are prefixed with ${pwd},
+ which process_wrapper and build_script_runner will expand at run time
+ to the absolute path.
+
+ See [`expand_location`](https://docs.bazel.build/versions/main/skylark/lib/ctx.html#expand_location) for detailed documentation.
+
+ Args:
+ ctx (ctx): The rule's context object
+ args (list): A list we iterate over
+ data (sequence of Targets): The targets which may be referenced by
+ location macros. This is expected to be the `data` attribute of
+ the target, though may have other targets or attributes mixed in.
+
+ Returns:
+ list: A list of arguments with expanded location macros
+ """
+ return [_expand_location(ctx, arg, data) for arg in args]
+
+def name_to_crate_name(name):
+ """Converts a build target's name into the name of its associated crate.
+
+ Crate names cannot contain certain characters, such as -, which are allowed
+ in build target names. All illegal characters will be converted to
+ underscores.
+
+ This is a similar conversion as that which cargo does, taking a
+ `Cargo.toml`'s `package.name` and canonicalizing it
+
+ Note that targets can specify the `crate_name` attribute to customize their
+ crate name; in situations where this is important, use the
+ compute_crate_name() function instead.
+
+ Args:
+ name (str): The name of the target.
+
+ Returns:
+ str: The name of the crate for this target.
+ """
+ return name.replace("-", "_")
+
+def _invalid_chars_in_crate_name(name):
+ """Returns any invalid chars in the given crate name.
+
+ Args:
+ name (str): Name to test.
+
+ Returns:
+ list: List of invalid characters in the crate name.
+ """
+
+ return dict([(c, ()) for c in name.elems() if not (c.isalnum() or c == "_")]).keys()
+
+def compute_crate_name(workspace_name, label, toolchain, name_override = None):
+ """Returns the crate name to use for the current target.
+
+ Args:
+ workspace_name (string): The current workspace name.
+ label (struct): The label of the current target.
+ toolchain (struct): The toolchain in use for the target.
+ name_override (String): An optional name to use (as an override of label.name).
+
+ Returns:
+ str: The crate name to use for this target.
+ """
+ if name_override:
+ invalid_chars = _invalid_chars_in_crate_name(name_override)
+ if invalid_chars:
+ fail("Crate name '{}' contains invalid character(s): {}".format(
+ name_override,
+ " ".join(invalid_chars),
+ ))
+ return name_override
+
+ if (toolchain and label and toolchain._rename_first_party_crates and
+ should_encode_label_in_crate_name(workspace_name, label, toolchain._third_party_dir)):
+ crate_name = encode_label_as_crate_name(label.package, label.name)
+ else:
+ crate_name = name_to_crate_name(label.name)
+
+ invalid_chars = _invalid_chars_in_crate_name(crate_name)
+ if invalid_chars:
+ fail(
+ "Crate name '{}' ".format(crate_name) +
+ "derived from Bazel target name '{}' ".format(label.name) +
+ "contains invalid character(s): {}\n".format(" ".join(invalid_chars)) +
+ "Consider adding a crate_name attribute to set a valid crate name",
+ )
+ return crate_name
+
+def dedent(doc_string):
+ """Remove any common leading whitespace from every line in text.
+
+ This functionality is similar to python's `textwrap.dedent` functionality
+ https://docs.python.org/3/library/textwrap.html#textwrap.dedent
+
+ Args:
+ doc_string (str): A docstring style string
+
+ Returns:
+ str: A string optimized for stardoc rendering
+ """
+ lines = doc_string.splitlines()
+ if not lines:
+ return doc_string
+
+ # If the first line is empty, use the second line
+ first_line = lines[0]
+ if not first_line:
+ first_line = lines[1]
+
+ # Detect how much space prepends the first line and subtract that from all lines
+ space_count = len(first_line) - len(first_line.lstrip())
+
+ # If there are no leading spaces, do not alter the docstring
+ if space_count == 0:
+ return doc_string
+ else:
+ # Remove the leading block of spaces from the current line
+ block = " " * space_count
+ return "\n".join([line.replace(block, "", 1).rstrip() for line in lines])
+
+def make_static_lib_symlink(actions, rlib_file):
+ """Add a .a symlink to an .rlib file.
+
+ The name of the symlink is derived from the <name> of the <name>.rlib file as follows:
+ * `<name>.a`, if <name> starts with `lib`
+ * `lib<name>.a`, otherwise.
+
+ For example, the name of the symlink for
+ * `libcratea.rlib` is `libcratea.a`
+ * `crateb.rlib` is `libcrateb.a`.
+
+ Args:
+ actions (actions): The rule's context actions object.
+ rlib_file (File): The file to symlink, which must end in .rlib.
+
+ Returns:
+ The symlink's File.
+ """
+ if not rlib_file.basename.endswith(".rlib"):
+ fail("file is not an .rlib: ", rlib_file.basename)
+ basename = rlib_file.basename[:-5]
+ if not basename.startswith("lib"):
+ basename = "lib" + basename
+ dot_a = actions.declare_file(basename + ".a", sibling = rlib_file)
+ actions.symlink(output = dot_a, target_file = rlib_file)
+ return dot_a
+
+def is_exec_configuration(ctx):
+ """Determine if a context is building for the exec configuration.
+
+ This is helpful when processing command line flags that should apply
+ to the target configuration but not the exec configuration.
+
+ Args:
+ ctx (ctx): The ctx object for the current target.
+
+ Returns:
+ True if the exec configuration is detected, False otherwise.
+ """
+
+ # TODO(djmarcin): Is there any better way to determine cfg=exec?
+ return ctx.genfiles_dir.path.find("-exec-") != -1
+
+def transform_deps(deps):
+ """Transforms a [Target] into [DepVariantInfo].
+
+ This helper function is used to transform ctx.attr.deps and ctx.attr.proc_macro_deps into
+ [DepVariantInfo].
+
+ Args:
+ deps (list of Targets): Dependencies coming from ctx.attr.deps or ctx.attr.proc_macro_deps
+
+ Returns:
+ list of DepVariantInfos.
+ """
+ return [DepVariantInfo(
+ crate_info = dep[CrateInfo] if CrateInfo in dep else None,
+ dep_info = dep[DepInfo] if DepInfo in dep else None,
+ build_info = dep[BuildInfo] if BuildInfo in dep else None,
+ cc_info = dep[CcInfo] if CcInfo in dep else None,
+ ) for dep in deps]
+
+def get_import_macro_deps(ctx):
+ """Returns a list of targets to be added to proc_macro_deps.
+
+ Args:
+ ctx (struct): the ctx of the current target.
+
+ Returns:
+ list of Targets. Either empty (if the fake import macro implementation
+ is being used), or a singleton list with the real implementation.
+ """
+ if ctx.attr._import_macro_dep.label.name == "fake_import_macro_impl":
+ return []
+
+ return [ctx.attr._import_macro_dep]
+
+def should_encode_label_in_crate_name(workspace_name, label, third_party_dir):
+ """Determines if the crate's name should include the Bazel label, encoded.
+
+ Crate names may only encode the label if the target is in the current repo,
+ the target is not in the third_party_dir, and the current repo is not
+ rules_rust.
+
+ Args:
+ workspace_name (string): The name of the current workspace.
+ label (Label): The package in question.
+ third_party_dir (string): The directory in which third-party packages are kept.
+
+ Returns:
+ True if the crate name should encode the label, False otherwise.
+ """
+
+ # TODO(hlopko): This code assumes a monorepo; make it work with external
+ # repositories as well.
+ return (
+ workspace_name != "rules_rust" and
+ not label.workspace_root and
+ not ("//" + label.package + "/").startswith(third_party_dir + "/")
+ )
+
+# This is a list of pairs, where the first element of the pair is a character
+# that is allowed in Bazel package or target names but not in crate names; and
+# the second element is an encoding of that char suitable for use in a crate
+# name.
+_encodings = (
+ (":", "colon"),
+ ("!", "bang"),
+ ("%", "percent"),
+ ("@", "at"),
+ ("^", "caret"),
+ ("`", "backtick"),
+ (" ", "space"),
+ ("\"", "quote"),
+ ("#", "hash"),
+ ("$", "dollar"),
+ ("&", "ampersand"),
+ ("'", "backslash"),
+ ("(", "lparen"),
+ (")", "rparen"),
+ ("*", "star"),
+ ("-", "dash"),
+ ("+", "plus"),
+ (",", "comma"),
+ (";", "semicolon"),
+ ("<", "langle"),
+ ("=", "equal"),
+ (">", "rangle"),
+ ("?", "question"),
+ ("[", "lbracket"),
+ ("]", "rbracket"),
+ ("{", "lbrace"),
+ ("|", "pipe"),
+ ("}", "rbrace"),
+ ("~", "tilde"),
+ ("/", "slash"),
+ (".", "dot"),
+)
+
+# For each of the above encodings, we generate two substitution rules: one that
+# ensures any occurrences of the encodings themselves in the package/target
+# aren't clobbered by this translation, and one that does the encoding itself.
+# We also include a rule that protects the clobbering-protection rules from
+# getting clobbered.
+_substitutions = [("_quote", "_quotequote_")] + [
+ subst
+ for (pattern, replacement) in _encodings
+ for subst in (
+ ("_{}_".format(replacement), "_quote{}_".format(replacement)),
+ (pattern, "_{}_".format(replacement)),
+ )
+]
+
+def encode_label_as_crate_name(package, name):
+ """Encodes the package and target names in a format suitable for a crate name.
+
+ Args:
+ package (string): The package of the target in question.
+ name (string): The name of the target in question.
+
+ Returns:
+ A string that encodes the package and target name, to be used as the crate's name.
+ """
+ full_name = package + ":" + name
+ return _replace_all(full_name, _substitutions)
+
+def decode_crate_name_as_label_for_testing(crate_name):
+ """Decodes a crate_name that was encoded by encode_label_as_crate_name.
+
+ This is used to check that the encoding is bijective; it is expected to only
+ be used in tests.
+
+ Args:
+ crate_name (string): The name of the crate.
+
+ Returns:
+ A string representing the Bazel label (package and target).
+ """
+ return _replace_all(crate_name, [(t[1], t[0]) for t in _substitutions])
+
+def _replace_all(string, substitutions):
+ """Replaces occurrences of the given patterns in `string`.
+
+ There are a few reasons this looks complicated:
+ * The substitutions are performed with some priority, i.e. patterns that are
+ listed first in `substitutions` are higher priority than patterns that are
+ listed later.
+ * We also take pains to avoid doing replacements that overlap with each
+ other, since overlaps invalidate pattern matches.
+ * To avoid hairy offset invalidation, we apply the substitutions
+ right-to-left.
+ * To avoid the "_quote" -> "_quotequote_" rule introducing new pattern
+ matches later in the string during decoding, we take the leftmost
+ replacement, in cases of overlap. (Note that no rule can induce new
+ pattern matches *earlier* in the string.) (E.g. "_quotedot_" encodes to
+ "_quotequote_dot_". Note that "_quotequote_" and "_dot_" both occur in
+ this string, and overlap.).
+
+ Args:
+ string (string): the string in which the replacements should be performed.
+ substitutions: the list of patterns and replacements to apply.
+
+ Returns:
+ A string with the appropriate substitutions performed.
+ """
+
+ # Find the highest-priority pattern matches for each string index, going
+ # left-to-right and skipping indices that are already involved in a
+ # pattern match.
+ plan = {}
+ matched_indices_set = {}
+ for pattern_start in range(len(string)):
+ if pattern_start in matched_indices_set:
+ continue
+ for (pattern, replacement) in substitutions:
+ if not string.startswith(pattern, pattern_start):
+ continue
+ length = len(pattern)
+ plan[pattern_start] = (length, replacement)
+ matched_indices_set.update([(pattern_start + i, True) for i in range(length)])
+ break
+
+ # Execute the replacement plan, working from right to left.
+ for pattern_start in sorted(plan.keys(), reverse = True):
+ length, replacement = plan[pattern_start]
+ after_pattern = pattern_start + length
+ string = string[:pattern_start] + replacement + string[after_pattern:]
+
+ return string