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/cargo/BUILD.bazel b/cargo/BUILD.bazel
new file mode 100644
index 0000000..8772c31
--- /dev/null
+++ b/cargo/BUILD.bazel
@@ -0,0 +1,15 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+package(default_visibility = ["//visibility:public"])
+
+bzl_library(
+ name = "bzl_lib",
+ srcs = glob(["**/*.bzl"]),
+ deps = ["//cargo/private:bzl_lib"],
+)
+
+alias(
+ name = "rules",
+ actual = ":bzl_lib",
+ deprecation = "Please use the `@rules_rust//cargo:bzl_lib` target instead",
+)
diff --git a/cargo/bootstrap/BUILD.bazel b/cargo/bootstrap/BUILD.bazel
new file mode 100644
index 0000000..a607f60
--- /dev/null
+++ b/cargo/bootstrap/BUILD.bazel
@@ -0,0 +1,20 @@
+load("//rust:defs.bzl", "rust_binary")
+
+exports_files(
+ [
+ "bootstrap_installer.rs",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+# This target is only used to confirm the source code is buildable
+# in a `cargo_bootstrap_repository` rule.
+rust_binary(
+ name = "bootstrap_installer_bin",
+ srcs = [
+ "bootstrap_installer.rs",
+ ],
+ rustc_env = {
+ "RULES_RUST_CARGO_BOOTSTRAP_BINARY": "$(rootpath bootstrap_installer.rs)",
+ },
+)
diff --git a/cargo/bootstrap/bootstrap_installer.rs b/cargo/bootstrap/bootstrap_installer.rs
new file mode 100644
index 0000000..32cb137
--- /dev/null
+++ b/cargo/bootstrap/bootstrap_installer.rs
@@ -0,0 +1,35 @@
+//! A tool for installing bootstrapped Rust binaries into the requested paths.
+
+use std::{
+ env,
+ fs::{copy, create_dir_all},
+ path::PathBuf,
+};
+
+fn install() -> std::io::Result<u64> {
+ let binary = PathBuf::from(env!("RULES_RUST_CARGO_BOOTSTRAP_BINARY"));
+
+ // Consume only the first argument as the destination
+ let dest = PathBuf::from(
+ env::args()
+ .nth(1)
+ .expect("No destination argument provided"),
+ );
+
+ // Create the parent directory structure if it doesn't exist
+ if let Some(parent) = dest.parent() {
+ if !parent.exists() {
+ create_dir_all(parent)?;
+ }
+ }
+
+ // Copy the file to the requested destination
+ copy(binary, dest)
+}
+
+fn main() {
+ if let Err(err) = install() {
+ eprintln!("{:?}", err);
+ std::process::exit(1);
+ };
+}
diff --git a/cargo/cargo_bootstrap.bzl b/cargo/cargo_bootstrap.bzl
new file mode 100644
index 0000000..bbe35c0
--- /dev/null
+++ b/cargo/cargo_bootstrap.bzl
@@ -0,0 +1,339 @@
+"""The `cargo_bootstrap` rule is used for bootstrapping cargo binaries in a repository rule."""
+
+load("//cargo/private:cargo_utils.bzl", "get_host_triple", "get_rust_tools")
+load("//rust:defs.bzl", "rust_common")
+
+_CARGO_BUILD_MODES = [
+ "release",
+ "debug",
+]
+
+_FAIL_MESSAGE = """\
+Process exited with code '{code}'
+# ARGV ########################################################################
+{argv}
+
+# STDOUT ######################################################################
+{stdout}
+
+# STDERR ######################################################################
+{stderr}
+"""
+
+def cargo_bootstrap(
+ repository_ctx,
+ cargo_bin,
+ rustc_bin,
+ binary,
+ cargo_manifest,
+ environment = {},
+ quiet = False,
+ build_mode = "release",
+ target_dir = None,
+ timeout = 600):
+ """A function for bootstrapping a cargo binary within a repository rule
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+ cargo_bin (path): The path to a Cargo binary.
+ rustc_bin (path): The path to a Rustc binary.
+ binary (str): The binary to build (the `--bin` parameter for Cargo).
+ cargo_manifest (path): The path to a Cargo manifest (Cargo.toml file).
+ environment (dict): Environment variables to use during execution.
+ quiet (bool, optional): Whether or not to print output from the Cargo command.
+ build_mode (str, optional): The build mode to use
+ target_dir (path, optional): The directory in which to produce build outputs
+ (Cargo's --target-dir argument).
+ timeout (int, optional): Maximum duration of the Cargo build command in seconds,
+
+ Returns:
+ path: The path of the built binary within the target directory
+ """
+
+ if not target_dir:
+ target_dir = repository_ctx.path(".")
+
+ args = [
+ cargo_bin,
+ "build",
+ "--bin",
+ binary,
+ "--locked",
+ "--target-dir",
+ target_dir,
+ "--manifest-path",
+ cargo_manifest,
+ ]
+
+ if build_mode not in _CARGO_BUILD_MODES:
+ fail("'{}' is not a supported build mode. Use one of {}".format(build_mode, _CARGO_BUILD_MODES))
+
+ if build_mode == "release":
+ args.append("--release")
+
+ env = dict({
+ "RUSTC": str(rustc_bin),
+ }.items() + environment.items())
+
+ repository_ctx.report_progress("Cargo Bootstrapping {}".format(binary))
+ result = repository_ctx.execute(
+ args,
+ environment = env,
+ quiet = quiet,
+ timeout = timeout,
+ )
+
+ if result.return_code != 0:
+ fail(_FAIL_MESSAGE.format(
+ code = result.return_code,
+ argv = args,
+ stdout = result.stdout,
+ stderr = result.stderr,
+ ))
+
+ extension = ""
+ if "win" in repository_ctx.os.name:
+ extension = ".exe"
+
+ binary_path = "{}/{}{}".format(
+ build_mode,
+ binary,
+ extension,
+ )
+
+ if not repository_ctx.path(binary_path).exists:
+ fail("Failed to produce binary at {}".format(binary_path))
+
+ return binary_path
+
+_BUILD_FILE_CONTENT = """\
+load("@rules_rust//rust:defs.bzl", "rust_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+exports_files([
+ "{binary_name}",
+ "{binary}"
+])
+
+alias(
+ name = "binary",
+ actual = "{binary}",
+)
+
+rust_binary(
+ name = "install",
+ rustc_env = {{
+ "RULES_RUST_CARGO_BOOTSTRAP_BINARY": "$(rootpath {binary})"
+ }},
+ data = [
+ "{binary}",
+ ],
+ srcs = [
+ "@rules_rust//cargo/bootstrap:bootstrap_installer.rs"
+ ],
+)
+"""
+
+def _collect_environ(repository_ctx, host_triple):
+ """Gather environment varialbes to use from the current rule context
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+ host_triple (str): A string of the current host triple
+
+ Returns:
+ dict: A map of environment variables
+ """
+ env_vars = dict(json.decode(repository_ctx.attr.env.get(host_triple, "{}")))
+
+ # Gather the path for each label and ensure it exists
+ env_labels = dict(json.decode(repository_ctx.attr.env_label.get(host_triple, "{}")))
+ env_labels = {key: repository_ctx.path(Label(value)) for (key, value) in env_labels.items()}
+ for key in env_labels:
+ if not env_labels[key].exists:
+ fail("File for key '{}' does not exist: {}", key, env_labels[key])
+ env_labels = {key: str(value) for (key, value) in env_labels.items()}
+
+ return dict(env_vars.items() + env_labels.items())
+
+def _detect_changes(repository_ctx):
+ """Inspect files that are considered inputs to the build for changes
+
+ Args:
+ repository_ctx (repository_ctx): The rule's context object.
+ """
+ # Simply generating a `path` object consideres the file as 'tracked' or
+ # 'consumed' which means changes to it will trigger rebuilds
+
+ for src in repository_ctx.attr.srcs:
+ repository_ctx.path(src)
+
+ repository_ctx.path(repository_ctx.attr.cargo_lockfile)
+ repository_ctx.path(repository_ctx.attr.cargo_toml)
+
+def _cargo_bootstrap_repository_impl(repository_ctx):
+ # Pretend to Bazel that this rule's input files have been used, so that it will re-run the rule if they change.
+ _detect_changes(repository_ctx)
+
+ if repository_ctx.attr.version in ("beta", "nightly"):
+ version_str = "{}-{}".format(repository_ctx.attr.version, repository_ctx.attr.iso_date)
+ else:
+ version_str = repository_ctx.attr.version
+
+ host_triple = get_host_triple(repository_ctx)
+
+ if repository_ctx.attr.rust_toolchain_repository_template:
+ # buildifier: disable=print
+ print("Warning: `rust_toolchain_repository_template` is deprecated. Please use `rust_toolchain_cargo_template` and `rust_toolchain_rustc_template`")
+ cargo_template = "@{}{}".format(repository_ctx.attr.rust_toolchain_repository_template, "//:bin/{tool}")
+ rustc_template = "@{}{}".format(repository_ctx.attr.rust_toolchain_repository_template, "//:bin/{tool}")
+ else:
+ cargo_template = repository_ctx.attr.rust_toolchain_cargo_template
+ rustc_template = repository_ctx.attr.rust_toolchain_rustc_template
+
+ tools = get_rust_tools(
+ cargo_template = cargo_template,
+ rustc_template = rustc_template,
+ host_triple = host_triple,
+ version = version_str,
+ )
+
+ binary_name = repository_ctx.attr.binary or repository_ctx.name
+
+ # In addition to platform specific environment variables, a common set (indicated by `*`) will always
+ # be gathered.
+ environment = dict(_collect_environ(repository_ctx, "*").items() + _collect_environ(repository_ctx, host_triple.triple).items())
+
+ built_binary = cargo_bootstrap(
+ repository_ctx = repository_ctx,
+ cargo_bin = repository_ctx.path(tools.cargo),
+ rustc_bin = repository_ctx.path(tools.rustc),
+ binary = binary_name,
+ cargo_manifest = repository_ctx.path(repository_ctx.attr.cargo_toml),
+ build_mode = repository_ctx.attr.build_mode,
+ environment = environment,
+ timeout = repository_ctx.attr.timeout,
+ )
+
+ # Create a symlink so that the binary can be accesed via it's target name
+ repository_ctx.symlink(built_binary, binary_name)
+
+ repository_ctx.file("BUILD.bazel", _BUILD_FILE_CONTENT.format(
+ binary_name = binary_name,
+ binary = built_binary,
+ ))
+
+cargo_bootstrap_repository = repository_rule(
+ doc = "A rule for bootstrapping a Rust binary using [Cargo](https://doc.rust-lang.org/cargo/)",
+ implementation = _cargo_bootstrap_repository_impl,
+ attrs = {
+ "binary": attr.string(
+ doc = "The binary to build (the `--bin` parameter for Cargo). If left empty, the repository name will be used.",
+ ),
+ "build_mode": attr.string(
+ doc = "The build mode the binary should be built with",
+ values = [
+ "debug",
+ "release",
+ ],
+ default = "release",
+ ),
+ "cargo_lockfile": attr.label(
+ doc = "The lockfile of the crate_universe resolver",
+ allow_single_file = ["Cargo.lock"],
+ mandatory = True,
+ ),
+ "cargo_toml": attr.label(
+ doc = "The path of the crate_universe resolver manifest (`Cargo.toml` file)",
+ allow_single_file = ["Cargo.toml"],
+ mandatory = True,
+ ),
+ "env": attr.string_dict(
+ doc = (
+ "A mapping of platform triple to a set of environment variables. See " +
+ "[cargo_env](#cargo_env) for usage details. Additionally, the platform triple `*` applies to all platforms."
+ ),
+ ),
+ "env_label": attr.string_dict(
+ doc = (
+ "A mapping of platform triple to a set of environment variables. This " +
+ "attribute differs from `env` in that all variables passed here must be " +
+ "fully qualified labels of files. See [cargo_env](#cargo_env) for usage details. " +
+ "Additionally, the platform triple `*` applies to all platforms."
+ ),
+ ),
+ "iso_date": attr.string(
+ doc = "The iso_date of cargo binary the resolver should use. Note: This can only be set if `version` is `beta` or `nightly`",
+ ),
+ "rust_toolchain_cargo_template": attr.string(
+ doc = (
+ "The template to use for finding the host `cargo` binary. `{version}` (eg. '1.53.0'), " +
+ "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
+ "`{system}` (eg. 'darwin'), and `{tool}` (eg. 'rustc.exe') will be replaced in the string if present."
+ ),
+ default = "@rust_{system}_{arch}//:bin/{tool}",
+ ),
+ "rust_toolchain_repository_template": attr.string(
+ doc = "**Deprecated**: Please use `rust_toolchain_cargo_template` and `rust_toolchain_rustc_template`",
+ ),
+ "rust_toolchain_rustc_template": attr.string(
+ doc = (
+ "The template to use for finding the host `rustc` binary. `{version}` (eg. '1.53.0'), " +
+ "`{triple}` (eg. 'x86_64-unknown-linux-gnu'), `{arch}` (eg. 'aarch64'), `{vendor}` (eg. 'unknown'), " +
+ "`{system}` (eg. 'darwin'), and `{tool}` (eg. 'rustc.exe') will be replaced in the string if present."
+ ),
+ default = "@rust_{system}_{arch}//:bin/{tool}",
+ ),
+ "srcs": attr.label_list(
+ doc = "Souce files of the crate to build. Passing source files here can be used to trigger rebuilds when changes are made",
+ allow_files = True,
+ ),
+ "timeout": attr.int(
+ doc = "Maximum duration of the Cargo build command in seconds",
+ default = 600,
+ ),
+ "version": attr.string(
+ doc = "The version of cargo the resolver should use",
+ default = rust_common.default_version,
+ ),
+ "_cc_toolchain": attr.label(
+ default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+ ),
+ },
+)
+
+def cargo_env(env):
+ """A helper for generating platform specific environment variables
+
+ ```python
+ load("@rules_rust//rust:defs.bzl", "rust_common")
+ load("@rules_rust//cargo:defs.bzl", "cargo_bootstrap_repository", "cargo_env")
+
+ cargo_bootstrap_repository(
+ name = "bootstrapped_bin",
+ cargo_lockfile = "//:Cargo.lock",
+ cargo_toml = "//:Cargo.toml",
+ srcs = ["//:resolver_srcs"],
+ version = rust_common.default_version,
+ binary = "my-crate-binary",
+ env = {
+ "x86_64-unknown-linux-gnu": cargo_env({
+ "FOO": "BAR",
+ }),
+ },
+ env_label = {
+ "aarch64-unknown-linux-musl": cargo_env({
+ "DOC": "//:README.md",
+ }),
+ }
+ )
+ ```
+
+ Args:
+ env (dict): A map of environment variables
+
+ Returns:
+ str: A json encoded string of the environment variables
+ """
+ return json.encode(dict(env))
diff --git a/cargo/cargo_build_script.bzl b/cargo/cargo_build_script.bzl
new file mode 100644
index 0000000..69450a2
--- /dev/null
+++ b/cargo/cargo_build_script.bzl
@@ -0,0 +1,396 @@
+# buildifier: disable=module-docstring
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "C_COMPILE_ACTION_NAME")
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+load("//rust:defs.bzl", "rust_binary", "rust_common")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:rustc.bzl", "BuildInfo", "get_compilation_mode_opts", "get_linker_and_args")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:utils.bzl", "dedent", "expand_dict_value_locations", "find_cc_toolchain", "find_toolchain", "name_to_crate_name")
+
+def get_cc_compile_env(cc_toolchain, feature_configuration):
+ """Gather cc environment variables from the given `cc_toolchain`
+
+ Args:
+ cc_toolchain (cc_toolchain): The current rule's `cc_toolchain`.
+ feature_configuration (FeatureConfiguration): Class used to construct command lines from CROSSTOOL features.
+
+ Returns:
+ dict: Returns environment variables to be set for given action.
+ """
+ compile_variables = cc_common.create_compile_variables(
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ )
+ return cc_common.get_environment_variables(
+ feature_configuration = feature_configuration,
+ action_name = C_COMPILE_ACTION_NAME,
+ variables = compile_variables,
+ )
+
+def _build_script_impl(ctx):
+ """The implementation for the `_build_script_run` rule.
+
+ Args:
+ ctx (ctx): The rules context object
+
+ Returns:
+ list: A list containing a BuildInfo provider
+ """
+ script = ctx.executable.script
+ toolchain = find_toolchain(ctx)
+ out_dir = ctx.actions.declare_directory(ctx.label.name + ".out_dir")
+ env_out = ctx.actions.declare_file(ctx.label.name + ".env")
+ dep_env_out = ctx.actions.declare_file(ctx.label.name + ".depenv")
+ flags_out = ctx.actions.declare_file(ctx.label.name + ".flags")
+ link_flags = ctx.actions.declare_file(ctx.label.name + ".linkflags")
+ link_search_paths = ctx.actions.declare_file(ctx.label.name + ".linksearchpaths") # rustc-link-search, propagated from transitive dependencies
+ manifest_dir = "%s.runfiles/%s/%s" % (script.path, ctx.label.workspace_name or ctx.workspace_name, ctx.label.package)
+ compilation_mode_opt_level = get_compilation_mode_opts(ctx, toolchain).opt_level
+
+ streams = struct(
+ stdout = ctx.actions.declare_file(ctx.label.name + ".stdout.log"),
+ stderr = ctx.actions.declare_file(ctx.label.name + ".stderr.log"),
+ )
+
+ pkg_name = _name_to_pkg_name(ctx.label.name)
+
+ toolchain_tools = [toolchain.all_files]
+
+ cc_toolchain = find_cpp_toolchain(ctx)
+
+ # Start with the default shell env, which contains any --action_env
+ # settings passed in on the command line.
+ env = dict(ctx.configuration.default_shell_env)
+
+ env.update({
+ "CARGO_CRATE_NAME": name_to_crate_name(pkg_name),
+ "CARGO_MANIFEST_DIR": manifest_dir,
+ "CARGO_PKG_NAME": pkg_name,
+ "HOST": toolchain.exec_triple,
+ "OPT_LEVEL": compilation_mode_opt_level,
+ "RUSTC": toolchain.rustc.path,
+ "TARGET": toolchain.target_flag_value,
+ # OUT_DIR is set by the runner itself, rather than on the action.
+ })
+
+ # This isn't exactly right, but Bazel doesn't have exact views of "debug" and "release", so...
+ env.update({
+ "DEBUG": {"dbg": "true", "fastbuild": "true", "opt": "false"}.get(ctx.var["COMPILATION_MODE"], "true"),
+ "PROFILE": {"dbg": "debug", "fastbuild": "debug", "opt": "release"}.get(ctx.var["COMPILATION_MODE"], "unknown"),
+ })
+
+ if ctx.attr.version:
+ version = ctx.attr.version.split("+")[0].split(".")
+ patch = version[2].split("-") if len(version) > 2 else [""]
+ env["CARGO_PKG_VERSION_MAJOR"] = version[0]
+ env["CARGO_PKG_VERSION_MINOR"] = version[1] if len(version) > 1 else ""
+ env["CARGO_PKG_VERSION_PATCH"] = patch[0]
+ env["CARGO_PKG_VERSION_PRE"] = patch[1] if len(patch) > 1 else ""
+ env["CARGO_PKG_VERSION"] = ctx.attr.version
+
+ # Pull in env vars which may be required for the cc_toolchain to work (e.g. on OSX, the SDK version).
+ # We hope that the linker env is sufficient for the whole cc_toolchain.
+ cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+ linker, link_args, linker_env = get_linker_and_args(ctx, ctx.attr, cc_toolchain, feature_configuration, None)
+ env.update(**linker_env)
+ env["LD"] = linker
+ env["LDFLAGS"] = " ".join(link_args)
+
+ # MSVC requires INCLUDE to be set
+ cc_env = get_cc_compile_env(cc_toolchain, feature_configuration)
+ include = cc_env.get("INCLUDE")
+ if include:
+ env["INCLUDE"] = include
+
+ if cc_toolchain:
+ toolchain_tools.append(cc_toolchain.all_files)
+
+ cc_executable = cc_toolchain.compiler_executable
+ if cc_executable:
+ env["CC"] = cc_executable
+ env["CXX"] = cc_executable
+ ar_executable = cc_toolchain.ar_executable
+ if ar_executable:
+ env["AR"] = ar_executable
+ if cc_toolchain.sysroot:
+ env["SYSROOT"] = cc_toolchain.sysroot
+
+ # Inform build scripts of rustc flags
+ # https://github.com/rust-lang/cargo/issues/9600
+ env["CARGO_ENCODED_RUSTFLAGS"] = "\\x1f".join([
+ # Allow build scripts to locate the generated sysroot
+ "--sysroot=${{pwd}}/{}".format(toolchain.sysroot),
+ ] + ctx.attr.rustc_flags)
+
+ for f in ctx.attr.crate_features:
+ env["CARGO_FEATURE_" + f.upper().replace("-", "_")] = "1"
+
+ env.update(expand_dict_value_locations(
+ ctx,
+ ctx.attr.build_script_env,
+ getattr(ctx.attr, "data", []) +
+ getattr(ctx.attr, "compile_data", []) +
+ getattr(ctx.attr, "tools", []),
+ ))
+
+ tools = depset(
+ direct = [
+ script,
+ ctx.executable._cargo_build_script_runner,
+ ] + ctx.files.data + ctx.files.tools + ([toolchain.target_json] if toolchain.target_json else []),
+ transitive = toolchain_tools,
+ )
+
+ links = ctx.attr.links or ""
+
+ # dep_env_file contains additional environment variables coming from
+ # direct dependency sys-crates' build scripts. These need to be made
+ # available to the current crate build script.
+ # See https://doc.rust-lang.org/cargo/reference/build-scripts.html#-sys-packages
+ # for details.
+ args = ctx.actions.args()
+ args.add_all([
+ script.path,
+ links,
+ out_dir.path,
+ env_out.path,
+ flags_out.path,
+ link_flags.path,
+ link_search_paths.path,
+ dep_env_out.path,
+ streams.stdout.path,
+ streams.stderr.path,
+ ])
+ build_script_inputs = []
+ for dep in ctx.attr.deps:
+ if rust_common.dep_info in dep and dep[rust_common.dep_info].dep_env:
+ dep_env_file = dep[rust_common.dep_info].dep_env
+ args.add(dep_env_file.path)
+ build_script_inputs.append(dep_env_file)
+ for dep_build_info in dep[rust_common.dep_info].transitive_build_infos.to_list():
+ build_script_inputs.append(dep_build_info.out_dir)
+
+ ctx.actions.run(
+ executable = ctx.executable._cargo_build_script_runner,
+ arguments = [args],
+ outputs = [out_dir, env_out, flags_out, link_flags, link_search_paths, dep_env_out, streams.stdout, streams.stderr],
+ tools = tools,
+ inputs = build_script_inputs,
+ mnemonic = "CargoBuildScriptRun",
+ progress_message = "Running Cargo build script {}".format(pkg_name),
+ env = env,
+ )
+
+ return [
+ BuildInfo(
+ out_dir = out_dir,
+ rustc_env = env_out,
+ dep_env = dep_env_out,
+ flags = flags_out,
+ link_flags = link_flags,
+ link_search_paths = link_search_paths,
+ ),
+ OutputGroupInfo(streams = depset([streams.stdout, streams.stderr])),
+ ]
+
+_build_script_run = rule(
+ doc = (
+ "A rule for running a crate's `build.rs` files to generate build information " +
+ "which is then used to determine how to compile said crate."
+ ),
+ implementation = _build_script_impl,
+ attrs = {
+ "build_script_env": attr.string_dict(
+ doc = "Environment variables for build scripts.",
+ ),
+ "crate_features": attr.string_list(
+ doc = "The list of rust features that the build script should consider activated.",
+ ),
+ "data": attr.label_list(
+ doc = "Data required by the build script.",
+ allow_files = True,
+ ),
+ "deps": attr.label_list(
+ doc = "The Rust dependencies of the crate",
+ providers = [rust_common.dep_info],
+ ),
+ "links": attr.string(
+ doc = "The name of the native library this crate links against.",
+ ),
+ "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)`.
+ """),
+ ),
+ # The source of truth will be the `cargo_build_script` macro until stardoc
+ # implements documentation inheritence. See https://github.com/bazelbuild/stardoc/issues/27
+ "script": attr.label(
+ doc = "The binary script to run, generally a `rust_binary` target.",
+ executable = True,
+ allow_files = True,
+ mandatory = True,
+ cfg = "exec",
+ ),
+ "tools": attr.label_list(
+ doc = "Tools required by the build script.",
+ allow_files = True,
+ cfg = "exec",
+ ),
+ "version": attr.string(
+ doc = "The semantic version (semver) of the crate",
+ ),
+ "_cargo_build_script_runner": attr.label(
+ executable = True,
+ allow_files = True,
+ default = Label("//cargo/cargo_build_script_runner:cargo_build_script_runner"),
+ cfg = "exec",
+ ),
+ "_cc_toolchain": attr.label(
+ default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+ ),
+ },
+ fragments = ["cpp"],
+ toolchains = [
+ str(Label("//rust:toolchain")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ incompatible_use_toolchain_transition = True,
+)
+
+def cargo_build_script(
+ name,
+ crate_features = [],
+ version = None,
+ deps = [],
+ build_script_env = {},
+ data = [],
+ tools = [],
+ links = None,
+ rustc_env = {},
+ rustc_flags = [],
+ visibility = None,
+ tags = None,
+ **kwargs):
+ """Compile and execute a rust build script to generate build attributes
+
+ This rules take the same arguments as rust_binary.
+
+ Example:
+
+ Suppose you have a crate with a cargo build script `build.rs`:
+
+ ```output
+ [workspace]/
+ hello_lib/
+ BUILD
+ build.rs
+ src/
+ lib.rs
+ ```
+
+ Then you want to use the build script in the following:
+
+ `hello_lib/BUILD`:
+ ```python
+ package(default_visibility = ["//visibility:public"])
+
+ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library")
+ load("@rules_rust//cargo:cargo_build_script.bzl", "cargo_build_script")
+
+ # This will run the build script from the root of the workspace, and
+ # collect the outputs.
+ cargo_build_script(
+ name = "build_script",
+ srcs = ["build.rs"],
+ # Optional environment variables passed during build.rs compilation
+ rustc_env = {
+ "CARGO_PKG_VERSION": "0.1.2",
+ },
+ # Optional environment variables passed during build.rs execution.
+ # Note that as the build script's working directory is not execroot,
+ # execpath/location will return an absolute path, instead of a relative
+ # one.
+ build_script_env = {
+ "SOME_TOOL_OR_FILE": "$(execpath @tool//:binary)"
+ }
+ # Optional data/tool dependencies
+ data = ["@tool//:binary"],
+ )
+
+ rust_library(
+ name = "hello_lib",
+ srcs = [
+ "src/lib.rs",
+ ],
+ deps = [":build_script"],
+ )
+ ```
+
+ The `hello_lib` target will be build with the flags and the environment variables declared by the \
+ build script in addition to the file generated by it.
+
+ Args:
+ name (str): The name for the underlying rule. This should be the name of the package being compiled, optionally with a suffix of _build_script.
+ crate_features (list, optional): A list of features to enable for the build script.
+ version (str, optional): The semantic version (semver) of the crate.
+ deps (list, optional): The dependencies of the crate.
+ build_script_env (dict, optional): Environment variables for build scripts.
+ data (list, optional): Files needed by the build script.
+ tools (list, optional): Tools (executables) needed by the build script.
+ links (str, optional): Name of the native library this crate links against.
+ rustc_env (dict, optional): Environment variables to set in rustc when compiling the build script.
+ rustc_flags (list, optional): List of compiler flags passed to `rustc`.
+ visibility (list of label, optional): Visibility to apply to the generated build script output.
+ tags: (list of str, optional): Tags to apply to the generated build script output.
+ **kwargs: Forwards to the underlying `rust_binary` rule.
+ """
+
+ # This duplicates the code in _build_script_impl because we need to make these available both when we invoke rustc (this code) and when we run the compiled build script (_build_script_impl).
+ # https://github.com/bazelbuild/rules_rust/issues/661 will hopefully remove this duplication.
+ rustc_env = dict(rustc_env)
+ if "CARGO_PKG_NAME" not in rustc_env:
+ rustc_env["CARGO_PKG_NAME"] = _name_to_pkg_name(name)
+ if "CARGO_CRATE_NAME" not in rustc_env:
+ rustc_env["CARGO_CRATE_NAME"] = name_to_crate_name(_name_to_pkg_name(name))
+
+ binary_tags = [tag for tag in tags or []]
+ if "manual" not in binary_tags:
+ binary_tags.append("manual")
+
+ rust_binary(
+ name = name + "_",
+ crate_features = crate_features,
+ version = version,
+ deps = deps,
+ data = data,
+ rustc_env = rustc_env,
+ rustc_flags = rustc_flags,
+ tags = binary_tags,
+ **kwargs
+ )
+ _build_script_run(
+ name = name,
+ script = ":{}_".format(name),
+ crate_features = crate_features,
+ version = version,
+ build_script_env = build_script_env,
+ links = links,
+ deps = deps,
+ data = data,
+ tools = tools,
+ rustc_flags = rustc_flags,
+ visibility = visibility,
+ tags = tags,
+ )
+
+def _name_to_pkg_name(name):
+ if name.endswith("_build_script"):
+ return name[:-len("_build_script")]
+ return name
diff --git a/cargo/cargo_build_script_runner/BUILD.bazel b/cargo/cargo_build_script_runner/BUILD.bazel
new file mode 100644
index 0000000..11edd45
--- /dev/null
+++ b/cargo/cargo_build_script_runner/BUILD.bazel
@@ -0,0 +1,24 @@
+load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
+
+rust_library(
+ name = "cargo_build_script_output_parser",
+ srcs = ["lib.rs"],
+)
+
+rust_test(
+ name = "test",
+ crate = ":cargo_build_script_output_parser",
+)
+
+rust_binary(
+ name = "cargo_build_script_runner",
+ srcs = ["bin.rs"],
+ visibility = ["//visibility:public"],
+ deps = [":cargo_build_script_output_parser"],
+)
+
+rust_test(
+ name = "bin_test",
+ crate = ":cargo_build_script_runner",
+ deps = [":cargo_build_script_runner"],
+)
diff --git a/cargo/cargo_build_script_runner/bin.rs b/cargo/cargo_build_script_runner/bin.rs
new file mode 100644
index 0000000..58f0363
--- /dev/null
+++ b/cargo/cargo_build_script_runner/bin.rs
@@ -0,0 +1,345 @@
+// 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.
+
+// A simple wrapper around a build_script execution to generate file to reuse
+// by rust_library/rust_binary.
+extern crate cargo_build_script_output_parser;
+
+use cargo_build_script_output_parser::{BuildScriptOutput, CompileAndLinkFlags};
+use std::collections::BTreeMap;
+use std::env;
+use std::fs::{create_dir_all, read_to_string, write};
+use std::path::Path;
+use std::process::Command;
+
+fn run_buildrs() -> Result<(), String> {
+ // We use exec_root.join rather than std::fs::canonicalize, to avoid resolving symlinks, as
+ // some execution strategies and remote execution environments may use symlinks in ways which
+ // canonicalizing them may break them, e.g. by having input files be symlinks into a /cas
+ // directory - resolving these may cause tools which inspect $0, or try to resolve files
+ // relative to themselves, to fail.
+ let exec_root = env::current_dir().expect("Failed to get current directory");
+ let manifest_dir_env = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR was not set");
+ let rustc_env = env::var("RUSTC").expect("RUSTC was not set");
+ let manifest_dir = exec_root.join(&manifest_dir_env);
+ let rustc = exec_root.join(&rustc_env);
+ let Options {
+ progname,
+ crate_links,
+ out_dir,
+ env_file,
+ compile_flags_file,
+ link_flags_file,
+ link_search_paths_file,
+ output_dep_env_path,
+ stdout_path,
+ stderr_path,
+ input_dep_env_paths,
+ } = parse_args()?;
+
+ let out_dir_abs = exec_root.join(&out_dir);
+ // For some reason Google's RBE does not create the output directory, force create it.
+ create_dir_all(&out_dir_abs)
+ .unwrap_or_else(|_| panic!("Failed to make output directory: {:?}", out_dir_abs));
+
+ let target_env_vars =
+ get_target_env_vars(&rustc_env).expect("Error getting target env vars from rustc");
+
+ let mut command = Command::new(exec_root.join(&progname));
+ command
+ .current_dir(&manifest_dir)
+ .envs(target_env_vars)
+ .env("OUT_DIR", out_dir_abs)
+ .env("CARGO_MANIFEST_DIR", manifest_dir)
+ .env("RUSTC", rustc)
+ .env("RUST_BACKTRACE", "full");
+
+ for dep_env_path in input_dep_env_paths.iter() {
+ if let Ok(contents) = read_to_string(dep_env_path) {
+ for line in contents.split('\n') {
+ // split on empty contents will still produce a single empty string in iterable.
+ if line.is_empty() {
+ continue;
+ }
+ let mut key_val = line.splitn(2, '=');
+ match (key_val.next(), key_val.next()) {
+ (Some(key), Some(value)) => {
+ command.env(key, value.replace("${pwd}", &exec_root.to_string_lossy()));
+ }
+ _ => {
+ return Err(
+ "error: Wrong environment file format, should not happen".to_owned()
+ )
+ }
+ }
+ }
+ } else {
+ return Err("error: Dependency environment file unreadable".to_owned());
+ }
+ }
+
+ for compiler_env_var in &["CC", "CXX"] {
+ if let Some(compiler_path) = env::var_os(compiler_env_var) {
+ let mut compiler_path = exec_root.join(compiler_path).into_os_string();
+ if let Some(sysroot_path) = env::var_os("SYSROOT") {
+ compiler_path.push(" --sysroot=");
+ compiler_path.push(&exec_root.join(sysroot_path));
+ }
+ command.env(compiler_env_var, compiler_path);
+ }
+ }
+
+ if let Some(ar_path) = env::var_os("AR") {
+ // The default OSX toolchain uses libtool as ar_executable not ar.
+ // This doesn't work when used as $AR, so simply don't set it - tools will probably fall back to
+ // /usr/bin/ar which is probably good enough.
+ if Path::new(&ar_path).file_name() == Some("libtool".as_ref()) {
+ command.env_remove("AR");
+ } else {
+ command.env("AR", exec_root.join(ar_path));
+ }
+ }
+
+ if let Some(ld_path) = env::var_os("LD") {
+ command.env("LD", exec_root.join(ld_path));
+ }
+
+ // replace env vars with a ${pwd} prefix with the exec_root
+ for (key, value) in env::vars() {
+ let exec_root_str = exec_root.to_str().expect("exec_root not in utf8");
+ if value.contains("${pwd}") {
+ env::set_var(key, value.replace("${pwd}", exec_root_str));
+ }
+ }
+
+ // Bazel does not support byte strings so in order to correctly represent `CARGO_ENCODED_RUSTFLAGS`
+ // the escaped `\x1f` sequences need to be unescaped
+ if let Ok(encoded_rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
+ command.env(
+ "CARGO_ENCODED_RUSTFLAGS",
+ encoded_rustflags.replace("\\x1f", "\x1f"),
+ );
+ }
+
+ let (buildrs_outputs, process_output) = BuildScriptOutput::outputs_from_command(&mut command)
+ .map_err(|process_output| {
+ format!(
+ "Build script process failed{}\n--stdout:\n{}\n--stderr:\n{}",
+ if let Some(exit_code) = process_output.status.code() {
+ format!(" with exit code {}", exit_code)
+ } else {
+ String::new()
+ },
+ String::from_utf8(process_output.stdout)
+ .expect("Failed to parse stdout of child process"),
+ String::from_utf8(process_output.stderr)
+ .expect("Failed to parse stdout of child process"),
+ )
+ })?;
+
+ write(
+ &env_file,
+ BuildScriptOutput::outputs_to_env(&buildrs_outputs, &exec_root.to_string_lossy())
+ .as_bytes(),
+ )
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", env_file));
+ write(
+ &output_dep_env_path,
+ BuildScriptOutput::outputs_to_dep_env(
+ &buildrs_outputs,
+ &crate_links,
+ &exec_root.to_string_lossy(),
+ )
+ .as_bytes(),
+ )
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", output_dep_env_path));
+ write(&stdout_path, process_output.stdout)
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", stdout_path));
+ write(&stderr_path, process_output.stderr)
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", stderr_path));
+
+ let CompileAndLinkFlags {
+ compile_flags,
+ link_flags,
+ link_search_paths,
+ } = BuildScriptOutput::outputs_to_flags(&buildrs_outputs, &exec_root.to_string_lossy());
+
+ write(&compile_flags_file, compile_flags.as_bytes())
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", compile_flags_file));
+ write(&link_flags_file, link_flags.as_bytes())
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_flags_file));
+ write(&link_search_paths_file, link_search_paths.as_bytes())
+ .unwrap_or_else(|_| panic!("Unable to write file {:?}", link_search_paths_file));
+ Ok(())
+}
+
+/// A representation of expected command line arguments.
+struct Options {
+ progname: String,
+ crate_links: String,
+ out_dir: String,
+ env_file: String,
+ compile_flags_file: String,
+ link_flags_file: String,
+ link_search_paths_file: String,
+ output_dep_env_path: String,
+ stdout_path: String,
+ stderr_path: String,
+ input_dep_env_paths: Vec<String>,
+}
+
+/// Parses positional comamnd line arguments into a well defined struct
+fn parse_args() -> Result<Options, String> {
+ let mut args = env::args().skip(1);
+
+ // TODO: we should consider an alternative to positional arguments.
+ match (args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next(), args.next()) {
+ (
+ Some(progname),
+ Some(crate_links),
+ Some(out_dir),
+ Some(env_file),
+ Some(compile_flags_file),
+ Some(link_flags_file),
+ Some(link_search_paths_file),
+ Some(output_dep_env_path),
+ Some(stdout_path),
+ Some(stderr_path),
+ ) => {
+ Ok(Options{
+ progname,
+ crate_links,
+ out_dir,
+ env_file,
+ compile_flags_file,
+ link_flags_file,
+ link_search_paths_file,
+ output_dep_env_path,
+ stdout_path,
+ stderr_path,
+ input_dep_env_paths: args.collect(),
+ })
+ }
+ _ => {
+ Err(format!("Usage: $0 progname crate_links out_dir env_file compile_flags_file link_flags_file link_search_paths_file output_dep_env_path stdout_path stderr_path input_dep_env_paths[arg1...argn]\nArguments passed: {:?}", args.collect::<Vec<String>>()))
+ }
+ }
+}
+
+fn get_target_env_vars<P: AsRef<Path>>(rustc: &P) -> Result<BTreeMap<String, String>, String> {
+ // As done by Cargo when constructing a cargo::core::compiler::build_context::target_info::TargetInfo.
+ let output = Command::new(rustc.as_ref())
+ .arg("--print=cfg")
+ .arg(format!(
+ "--target={}",
+ env::var("TARGET").expect("missing TARGET")
+ ))
+ .output()
+ .map_err(|err| format!("Error running rustc to get target information: {}", err))?;
+ if !output.status.success() {
+ return Err(format!(
+ "Error running rustc to get target information: {:?}",
+ output
+ ));
+ }
+ let stdout = std::str::from_utf8(&output.stdout)
+ .map_err(|err| format!("Non-UTF8 stdout from rustc: {:?}", err))?;
+
+ Ok(parse_rustc_cfg_output(stdout))
+}
+
+fn parse_rustc_cfg_output(stdout: &str) -> BTreeMap<String, String> {
+ let mut values = BTreeMap::new();
+
+ for line in stdout.lines() {
+ if line.starts_with("target_") && line.contains('=') {
+ let mut parts = line.splitn(2, '=');
+ // UNWRAP: Verified that line contains = and split into exactly 2 parts.
+ let key = parts.next().unwrap();
+ let value = parts.next().unwrap();
+ if value.starts_with('"') && value.ends_with('"') && value.len() >= 2 {
+ values
+ .entry(key)
+ .or_insert_with(Vec::new)
+ .push(value[1..(value.len() - 1)].to_owned());
+ }
+ } else if ["windows", "unix"].contains(&line) {
+ // the 'windows' or 'unix' line received from rustc will be turned
+ // into eg. CARGO_CFG_WINDOWS='' below
+ values.insert(line, vec![]);
+ }
+ }
+
+ values
+ .into_iter()
+ .map(|(key, value)| (format!("CARGO_CFG_{}", key.to_uppercase()), value.join(",")))
+ .collect()
+}
+
+fn main() {
+ std::process::exit(match run_buildrs() {
+ Ok(_) => 0,
+ Err(err) => {
+ // Neatly print errors
+ eprintln!("{}", err);
+ 1
+ }
+ });
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn rustc_cfg_parsing() {
+ let macos_output = r#"\
+debug_assertions
+target_arch="x86_64"
+target_endian="little"
+target_env=""
+target_family="unix"
+target_feature="fxsr"
+target_feature="sse"
+target_feature="sse2"
+target_feature="sse3"
+target_feature="ssse3"
+target_os="macos"
+target_pointer_width="64"
+target_vendor="apple"
+unix
+"#;
+ let tree = parse_rustc_cfg_output(macos_output);
+ assert_eq!(tree["CARGO_CFG_UNIX"], "");
+ assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "unix");
+
+ let windows_output = r#"\
+debug_assertions
+target_arch="x86_64"
+target_endian="little"
+target_env="msvc"
+target_family="windows"
+target_feature="fxsr"
+target_feature="sse"
+target_feature="sse2"
+target_os="windows"
+target_pointer_width="64"
+target_vendor="pc"
+windows
+"#;
+ let tree = parse_rustc_cfg_output(windows_output);
+ assert_eq!(tree["CARGO_CFG_WINDOWS"], "");
+ assert_eq!(tree["CARGO_CFG_TARGET_FAMILY"], "windows");
+ }
+}
diff --git a/cargo/cargo_build_script_runner/lib.rs b/cargo/cargo_build_script_runner/lib.rs
new file mode 100644
index 0000000..b1aa793
--- /dev/null
+++ b/cargo/cargo_build_script_runner/lib.rs
@@ -0,0 +1,291 @@
+// 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.
+
+//! Parse the output of a cargo build.rs script and generate a list of flags and
+//! environment variable for the build.
+use std::io::{BufRead, BufReader, Read};
+use std::process::{Command, Output};
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct CompileAndLinkFlags {
+ pub compile_flags: String,
+ pub link_flags: String,
+ pub link_search_paths: String,
+}
+
+/// Enum containing all the considered return value from the script
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum BuildScriptOutput {
+ /// cargo:rustc-link-lib
+ LinkLib(String),
+ /// cargo:rustc-link-search
+ LinkSearch(String),
+ /// cargo:rustc-cfg
+ Cfg(String),
+ /// cargo:rustc-flags
+ Flags(String),
+ /// cargo:rustc-link-arg
+ LinkArg(String),
+ /// cargo:rustc-env
+ Env(String),
+ /// cargo:VAR=VALUE
+ DepEnv(String),
+}
+
+impl BuildScriptOutput {
+ /// Converts a line into a [BuildScriptOutput] enum.
+ ///
+ /// Examples
+ /// ```rust
+ /// assert_eq!(BuildScriptOutput::new("cargo:rustc-link-lib=lib"), Some(BuildScriptOutput::LinkLib("lib".to_owned())));
+ /// ```
+ fn new(line: &str) -> Option<BuildScriptOutput> {
+ let split = line.splitn(2, '=').collect::<Vec<_>>();
+ if split.len() <= 1 {
+ // Not a cargo directive.
+ return None;
+ }
+ let param = split[1].trim().to_owned();
+ let key_split = split[0].splitn(2, ':').collect::<Vec<_>>();
+ if key_split.len() <= 1 || key_split[0] != "cargo" {
+ // Not a cargo directive.
+ return None;
+ }
+
+ match key_split[1] {
+ "rustc-link-lib" => Some(BuildScriptOutput::LinkLib(param)),
+ "rustc-link-search" => Some(BuildScriptOutput::LinkSearch(param)),
+ "rustc-cfg" => Some(BuildScriptOutput::Cfg(param)),
+ "rustc-flags" => Some(BuildScriptOutput::Flags(param)),
+ "rustc-link-arg" => Some(BuildScriptOutput::LinkArg(param)),
+ "rustc-env" => Some(BuildScriptOutput::Env(param)),
+ "rerun-if-changed" | "rerun-if-env-changed" =>
+ // Ignored because Bazel will re-run if those change all the time.
+ {
+ None
+ }
+ "warning" => {
+ eprint!("Build Script Warning: {}", split[1]);
+ None
+ }
+ "rustc-cdylib-link-arg" | "rustc-link-arg-bin" | "rustc-link-arg-bins" => {
+ // cargo:rustc-cdylib-link-arg=FLAG — Passes custom flags to a linker for cdylib crates.
+ // cargo:rustc-link-arg-bin=BIN=FLAG – Passes custom flags to a linker for the binary BIN.
+ // cargo:rustc-link-arg-bins=FLAG – Passes custom flags to a linker for binaries.
+ eprint!(
+ "Warning: build script returned unsupported directive `{}`",
+ split[0]
+ );
+ None
+ }
+ _ => {
+ // cargo:KEY=VALUE — Metadata, used by links scripts.
+ Some(BuildScriptOutput::DepEnv(format!(
+ "{}={}",
+ key_split[1].to_uppercase(),
+ param
+ )))
+ }
+ }
+ }
+
+ /// Converts a [BufReader] into a vector of [BuildScriptOutput] enums.
+ fn outputs_from_reader<T: Read>(mut reader: BufReader<T>) -> Vec<BuildScriptOutput> {
+ let mut result = Vec::<BuildScriptOutput>::new();
+ let mut line = String::new();
+ while reader.read_line(&mut line).expect("Cannot read line") != 0 {
+ if let Some(bso) = BuildScriptOutput::new(&line) {
+ result.push(bso);
+ }
+ line.clear();
+ }
+ result
+ }
+
+ /// Take a [Command], execute it and converts its input into a vector of [BuildScriptOutput]
+ pub fn outputs_from_command(
+ cmd: &mut Command,
+ ) -> Result<(Vec<BuildScriptOutput>, Output), Output> {
+ let child_output = cmd.output().expect("Unable to start binary");
+ if child_output.status.success() {
+ let reader = BufReader::new(child_output.stdout.as_slice());
+ let output = Self::outputs_from_reader(reader);
+ Ok((output, child_output))
+ } else {
+ Err(child_output)
+ }
+ }
+
+ /// Convert a vector of [BuildScriptOutput] into a list of environment variables.
+ pub fn outputs_to_env(outputs: &[BuildScriptOutput], exec_root: &str) -> String {
+ outputs
+ .iter()
+ .filter_map(|x| {
+ if let BuildScriptOutput::Env(env) = x {
+ Some(Self::escape_for_serializing(Self::redact_exec_root(
+ env, exec_root,
+ )))
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+
+ /// Convert a vector of [BuildScriptOutput] into a list of dependencies environment variables.
+ pub fn outputs_to_dep_env(
+ outputs: &[BuildScriptOutput],
+ crate_links: &str,
+ exec_root: &str,
+ ) -> String {
+ let prefix = format!("DEP_{}_", crate_links.replace('-', "_").to_uppercase());
+ outputs
+ .iter()
+ .filter_map(|x| {
+ if let BuildScriptOutput::DepEnv(env) = x {
+ Some(format!(
+ "{}{}",
+ prefix,
+ Self::escape_for_serializing(Self::redact_exec_root(env, exec_root))
+ ))
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>()
+ .join("\n")
+ }
+
+ /// Convert a vector of [BuildScriptOutput] into a flagfile.
+ pub fn outputs_to_flags(outputs: &[BuildScriptOutput], exec_root: &str) -> CompileAndLinkFlags {
+ let mut compile_flags = Vec::new();
+ let mut link_flags = Vec::new();
+ let mut link_search_paths = Vec::new();
+
+ for flag in outputs {
+ match flag {
+ BuildScriptOutput::Cfg(e) => compile_flags.push(format!("--cfg={}", e)),
+ BuildScriptOutput::Flags(e) => compile_flags.push(e.to_owned()),
+ BuildScriptOutput::LinkArg(e) => compile_flags.push(format!("-Clink-arg={}", e)),
+ BuildScriptOutput::LinkLib(e) => link_flags.push(format!("-l{}", e)),
+ BuildScriptOutput::LinkSearch(e) => link_search_paths.push(format!("-L{}", e)),
+ _ => {}
+ }
+ }
+
+ CompileAndLinkFlags {
+ compile_flags: compile_flags.join("\n"),
+ link_flags: Self::redact_exec_root(&link_flags.join("\n"), exec_root),
+ link_search_paths: Self::redact_exec_root(&link_search_paths.join("\n"), exec_root),
+ }
+ }
+
+ fn redact_exec_root(value: &str, exec_root: &str) -> String {
+ value.replace(exec_root, "${pwd}")
+ }
+
+ // The process-wrapper treats trailing backslashes as escapes for following newlines.
+ // If the env var ends with a backslash (and accordingly doesn't have a following newline),
+ // escape it so that it doesn't get turned into a newline by the process-wrapper.
+ //
+ // Note that this code doesn't handle newlines in strings - that's because Cargo treats build
+ // script output as single-line-oriented, so stops processing at the end of a line regardless.
+ fn escape_for_serializing(mut value: String) -> String {
+ if value.ends_with('\\') {
+ value.push('\\');
+ }
+ value
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::io::Cursor;
+
+ #[test]
+ fn test_from_read_buffer_to_env_and_flags() {
+ let buff = Cursor::new(
+ "
+cargo:rustc-link-lib=sdfsdf
+cargo:rustc-env=FOO=BAR
+cargo:rustc-link-search=/some/absolute/path/bleh
+cargo:rustc-env=BAR=FOO
+cargo:rustc-flags=-Lblah
+cargo:rerun-if-changed=ignored
+cargo:rustc-cfg=feature=awesome
+cargo:version=123
+cargo:version_number=1010107f
+cargo:include_path=/some/absolute/path/include
+cargo:rustc-env=SOME_PATH=/some/absolute/path/beep
+cargo:rustc-link-arg=-weak_framework
+cargo:rustc-link-arg=Metal
+",
+ );
+ let reader = BufReader::new(buff);
+ let result = BuildScriptOutput::outputs_from_reader(reader);
+ assert_eq!(result.len(), 12);
+ assert_eq!(result[0], BuildScriptOutput::LinkLib("sdfsdf".to_owned()));
+ assert_eq!(result[1], BuildScriptOutput::Env("FOO=BAR".to_owned()));
+ assert_eq!(
+ result[2],
+ BuildScriptOutput::LinkSearch("/some/absolute/path/bleh".to_owned())
+ );
+ assert_eq!(result[3], BuildScriptOutput::Env("BAR=FOO".to_owned()));
+ assert_eq!(result[4], BuildScriptOutput::Flags("-Lblah".to_owned()));
+ assert_eq!(
+ result[5],
+ BuildScriptOutput::Cfg("feature=awesome".to_owned())
+ );
+ assert_eq!(
+ result[6],
+ BuildScriptOutput::DepEnv("VERSION=123".to_owned())
+ );
+ assert_eq!(
+ result[7],
+ BuildScriptOutput::DepEnv("VERSION_NUMBER=1010107f".to_owned())
+ );
+ assert_eq!(
+ result[9],
+ BuildScriptOutput::Env("SOME_PATH=/some/absolute/path/beep".to_owned())
+ );
+ assert_eq!(
+ result[10],
+ BuildScriptOutput::LinkArg("-weak_framework".to_owned())
+ );
+ assert_eq!(result[11], BuildScriptOutput::LinkArg("Metal".to_owned()));
+
+ assert_eq!(
+ BuildScriptOutput::outputs_to_dep_env(&result, "ssh2", "/some/absolute/path"),
+ "DEP_SSH2_VERSION=123\nDEP_SSH2_VERSION_NUMBER=1010107f\nDEP_SSH2_INCLUDE_PATH=${pwd}/include".to_owned()
+ );
+ assert_eq!(
+ BuildScriptOutput::outputs_to_env(&result, "/some/absolute/path"),
+ "FOO=BAR\nBAR=FOO\nSOME_PATH=${pwd}/beep".to_owned()
+ );
+ assert_eq!(
+ BuildScriptOutput::outputs_to_flags(&result, "/some/absolute/path"),
+ CompileAndLinkFlags {
+ // -Lblah was output as a rustc-flags, so even though it probably _should_ be a link
+ // flag, we don't treat it like one.
+ compile_flags:
+ "-Lblah\n--cfg=feature=awesome\n-Clink-arg=-weak_framework\n-Clink-arg=Metal"
+ .to_owned(),
+ link_flags: "-lsdfsdf".to_owned(),
+ link_search_paths: "-L${pwd}/bleh".to_owned(),
+ }
+ );
+ }
+}
diff --git a/cargo/defs.bzl b/cargo/defs.bzl
new file mode 100644
index 0000000..0ca086b
--- /dev/null
+++ b/cargo/defs.bzl
@@ -0,0 +1,9 @@
+"""Common definitions for the `@rules_rust//cargo` package"""
+
+load(":cargo_bootstrap.bzl", _cargo_bootstrap_repository = "cargo_bootstrap_repository", _cargo_env = "cargo_env")
+load(":cargo_build_script.bzl", _cargo_build_script = "cargo_build_script")
+
+cargo_bootstrap_repository = _cargo_bootstrap_repository
+cargo_env = _cargo_env
+
+cargo_build_script = _cargo_build_script
diff --git a/cargo/private/BUILD.bazel b/cargo/private/BUILD.bazel
new file mode 100644
index 0000000..6a1aab6
--- /dev/null
+++ b/cargo/private/BUILD.bazel
@@ -0,0 +1,7 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+bzl_library(
+ name = "bzl_lib",
+ srcs = glob(["**/*.bzl"]),
+ visibility = ["//:__subpackages__"],
+)
diff --git a/cargo/private/cargo_utils.bzl b/cargo/private/cargo_utils.bzl
new file mode 100644
index 0000000..d519576
--- /dev/null
+++ b/cargo/private/cargo_utils.bzl
@@ -0,0 +1,193 @@
+"""Utility functions for the cargo rules"""
+
+load("//rust/platform:triple.bzl", "triple")
+load("//rust/platform:triple_mappings.bzl", "system_to_binary_ext")
+
+_CPU_ARCH_ERROR_MSG = """\
+Command failed with exit code '{code}': {args}
+----------stdout:
+{stdout}
+----------stderr:
+{stderr}
+"""
+
+def _query_cpu_architecture(repository_ctx, expected_archs, is_windows = False):
+ """Detect the host CPU architecture
+
+ Args:
+ repository_ctx (repository_ctx): The repository rule's context object
+ expected_archs (list): A list of expected architecture strings
+ is_windows (bool, optional): If true, the cpu lookup will use the windows method (`wmic` vs `uname`)
+
+ Returns:
+ str: The host's CPU architecture
+ """
+ if is_windows:
+ arguments = ["wmic", "os", "get", "osarchitecture"]
+ else:
+ arguments = ["uname", "-m"]
+
+ result = repository_ctx.execute(arguments)
+
+ if result.return_code:
+ fail(_CPU_ARCH_ERROR_MSG.format(
+ code = result.return_code,
+ args = arguments,
+ stdout = result.stdout,
+ stderr = result.stderr,
+ ))
+
+ if is_windows:
+ # Example output:
+ # OSArchitecture
+ # 64-bit
+ lines = result.stdout.split("\n")
+ arch = lines[1].strip()
+
+ # Translate 64-bit to a compatible rust platform
+ # https://doc.rust-lang.org/nightly/rustc/platform-support.html
+ if arch == "64-bit":
+ arch = "x86_64"
+ else:
+ arch = result.stdout.strip("\n")
+
+ # Correct the arm architecture for macos
+ if "mac" in repository_ctx.os.name and arch == "arm64":
+ arch = "aarch64"
+
+ if not arch in expected_archs:
+ fail("{} is not a expected cpu architecture {}\n{}".format(
+ arch,
+ expected_archs,
+ result.stdout,
+ ))
+
+ return arch
+
+def get_host_triple(repository_ctx, abi = None):
+ """Query host information for the appropriate triples for the crate_universe resolver
+
+ Args:
+ repository_ctx (repository_ctx): The rule's repository_ctx
+ abi (str): Since there's no consistent way to check for ABI, this info
+ may be explicitly provided
+
+ Returns:
+ struct: A triple struct, see `@rules_rust//rust/platform:triple.bzl`
+ """
+
+ # Detect the host's cpu architecture
+
+ supported_architectures = {
+ "linux": ["aarch64", "x86_64"],
+ "macos": ["aarch64", "x86_64"],
+ "windows": ["x86_64"],
+ }
+
+ if "linux" in repository_ctx.os.name:
+ cpu = _query_cpu_architecture(repository_ctx, supported_architectures["linux"])
+ return triple("{}-unknown-linux-{}".format(
+ cpu,
+ abi or "gnu",
+ ))
+
+ if "mac" in repository_ctx.os.name:
+ cpu = _query_cpu_architecture(repository_ctx, supported_architectures["macos"])
+ return triple("{}-apple-darwin".format(cpu))
+
+ if "win" in repository_ctx.os.name:
+ cpu = _query_cpu_architecture(repository_ctx, supported_architectures["windows"], True)
+ return triple("{}-pc-windows-{}".format(
+ cpu,
+ abi or "msvc",
+ ))
+
+ fail("Unhandled host os: {}", repository_ctx.os.name)
+
+def _resolve_repository_template(
+ template,
+ abi = None,
+ arch = None,
+ system = None,
+ tool = None,
+ triple = None,
+ vendor = None,
+ version = None):
+ """Render values into a repository template string
+
+ Args:
+ template (str): The template to use for rendering
+ abi (str, optional): The host ABI
+ arch (str, optional): The host CPU architecture
+ system (str, optional): The host system name
+ tool (str, optional): The tool to expect in the particular repository.
+ Eg. `cargo`, `rustc`, `stdlib`.
+ triple (str, optional): The host triple
+ vendor (str, optional): The host vendor name
+ version (str, optional): The Rust version used in the toolchain.
+ Returns:
+ string: The resolved template string based on the given parameters
+ """
+ if abi:
+ template = template.replace("{abi}", abi)
+
+ if arch:
+ template = template.replace("{arch}", arch)
+
+ if system:
+ template = template.replace("{system}", system)
+
+ if tool:
+ template = template.replace("{tool}", tool)
+
+ if triple:
+ template = template.replace("{triple}", triple)
+
+ if vendor:
+ template = template.replace("{vendor}", vendor)
+
+ if version:
+ template = template.replace("{version}", version)
+
+ return template
+
+def get_rust_tools(cargo_template, rustc_template, host_triple, version):
+ """Retrieve `cargo` and `rustc` labels based on the host triple.
+
+ Args:
+ cargo_template (str): A template used to identify the label of the host `cargo` binary.
+ rustc_template (str): A template used to identify the label of the host `rustc` binary.
+ host_triple (struct): The host's triple. See `@rules_rust//rust/platform:triple.bzl`.
+ version (str): The version of Cargo+Rustc to use.
+
+ Returns:
+ struct: A struct containing the labels of expected tools
+ """
+ extension = system_to_binary_ext(host_triple.system)
+
+ cargo_label = Label(_resolve_repository_template(
+ template = cargo_template,
+ version = version,
+ triple = host_triple.triple,
+ arch = host_triple.arch,
+ vendor = host_triple.vendor,
+ system = host_triple.system,
+ abi = host_triple.abi,
+ tool = "cargo" + extension,
+ ))
+
+ rustc_label = Label(_resolve_repository_template(
+ template = rustc_template,
+ version = version,
+ triple = host_triple.triple,
+ arch = host_triple.arch,
+ vendor = host_triple.vendor,
+ system = host_triple.system,
+ abi = host_triple.abi,
+ tool = "rustc" + extension,
+ ))
+
+ return struct(
+ cargo = cargo_label,
+ rustc = rustc_label,
+ )