From 2f03f47a7705793b319cf32686a7ee41682b2495 Mon Sep 17 00:00:00 2001 From: rejuvenile <2027618+rejuvenile@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:11:26 -0700 Subject: [PATCH] Add rust_system_toolchain_repository for pre-installed toolchains Introduce a repository rule that probes the system for an installed Rust toolchain and generates BUILD targets with sysroot_path set. This follows the cc_configure pattern from rules_cc: the repository rule runs at fetch time, discovers the toolchain, and generates appropriate targets. Changes: - New rust/private/system_rust_configure.bzl with: - rust_system_toolchain_repository repository rule - system_rust_ext module extension for bzlmod - System probing (rustc --print sysroot, --version, --print target-libdir) - BUILD file generation with rust_toolchain + toolchain targets - rust/toolchain.bzl: Add sysroot_path attribute to rust_toolchain. When set, sysroot files are excluded from action inputs and --sysroot is passed to rustc pointing at the system installation. - rust/private/rustc.bzl: Use system rustc path when sysroot_path is set - rust/private/clippy.bzl: Use system clippy-driver when sysroot_path is set - rust/private/rustdoc.bzl: Use system rustdoc when sysroot_path is set - rust/private/unpretty.bzl: Use system rustc when sysroot_path is set - cargo/private/cargo_build_script.bzl: Use system tool paths for CARGO, RUSTC, RUSTDOC env vars when sysroot_path is set This replaces the previous system_sysroot boolean approach with a cleaner separation: the repository rule handles system probing, and the toolchain rule simply accepts a sysroot_path string for path redirection. --- cargo/private/cargo_build_script.bzl | 9 +- rust/private/clippy.bzl | 4 +- rust/private/rustc.bzl | 14 +- rust/private/rustdoc.bzl | 9 +- rust/private/system_rust_configure.bzl | 376 +++++++++++++++++++++++++ rust/private/unpretty.bzl | 4 +- rust/toolchain.bzl | 68 +++-- 7 files changed, 457 insertions(+), 27 deletions(-) create mode 100644 rust/private/system_rust_configure.bzl diff --git a/cargo/private/cargo_build_script.bzl b/cargo/private/cargo_build_script.bzl index c23108851a..47603cf56d 100644 --- a/cargo/private/cargo_build_script.bzl +++ b/cargo/private/cargo_build_script.bzl @@ -416,7 +416,10 @@ def _cargo_build_script_impl(ctx): env.update(ctx.configuration.default_shell_env) if toolchain.cargo: - env["CARGO"] = "${pwd}/%s" % toolchain.cargo.path + if toolchain.sysroot_path: + env["CARGO"] = toolchain.sysroot_path + "/bin/cargo" + else: + env["CARGO"] = "${pwd}/%s" % toolchain.cargo.path env.update({ "CARGO_CRATE_NAME": name_to_crate_name(pkg_name), @@ -425,8 +428,8 @@ def _cargo_build_script_impl(ctx): "HOST": toolchain.exec_triple.str, "NUM_JOBS": "1", "OPT_LEVEL": compilation_mode_opt_level, - "RUSTC": toolchain.rustc.path, - "RUSTDOC": toolchain.rust_doc.path, + "RUSTC": (toolchain.sysroot_path + "/bin/rustc") if toolchain.sysroot_path else toolchain.rustc.path, + "RUSTDOC": (toolchain.sysroot_path + "/bin/rustdoc") if toolchain.sysroot_path else toolchain.rust_doc.path, "TARGET": toolchain.target_flag_value, # OUT_DIR is set by the runner itself, rather than on the action. }) diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl index 318c05af8d..5621e93faf 100644 --- a/rust/private/clippy.bzl +++ b/rust/private/clippy.bzl @@ -160,12 +160,14 @@ def rust_clippy_action(ctx, clippy_executable, process_wrapper, crate_info, conf crate_info_dict["rustc_output"] = clippy_diagnostics_file crate_info = rust_common.create_crate_info(**crate_info_dict) + clippy_tool_path = (toolchain.sysroot_path + "/bin/clippy-driver") if toolchain.sysroot_path else clippy_executable.path + args, env = construct_arguments( ctx = ctx, attr = ctx.rule.attr, file = ctx.file, toolchain = toolchain, - tool_path = clippy_executable.path, + tool_path = clippy_tool_path, cc_toolchain = cc_toolchain, feature_configuration = feature_configuration, crate_info = crate_info, diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl index 34e19bd4d1..6f12294be3 100644 --- a/rust/private/rustc.bzl +++ b/rust/private/rustc.bzl @@ -1193,8 +1193,10 @@ def construct_arguments( {}, )) - # Ensure the sysroot is set for the target platform - if toolchain._toolchain_generated_sysroot: + # Ensure the sysroot is set for the target platform. + # When sysroot_path is set (system toolchain), always pass --sysroot so rustc + # finds the standard library at the pre-installed location. + if toolchain.sysroot_path or toolchain._toolchain_generated_sysroot: rustc_flags.add(toolchain.sysroot, format = "--sysroot=%s") if toolchain._rename_first_party_crates: @@ -1400,12 +1402,16 @@ def rustc_compile_action( elif ctx.attr.require_explicit_unstable_features == -1: require_explicit_unstable_features = toolchain.require_explicit_unstable_features + # When using a system sysroot, use the system rustc path instead of the + # sysroot-symlinked binary. + rustc_tool_path = (toolchain.sysroot_path + "/bin/rustc") if toolchain.sysroot_path else toolchain.rustc.path + args, env_from_args = construct_arguments( ctx = ctx, attr = attr, file = ctx.file, toolchain = toolchain, - tool_path = toolchain.rustc.path, + tool_path = rustc_tool_path, cc_toolchain = cc_toolchain, emit = emit, feature_configuration = feature_configuration, @@ -1432,7 +1438,7 @@ def rustc_compile_action( attr = attr, file = ctx.file, toolchain = toolchain, - tool_path = toolchain.rustc.path, + tool_path = rustc_tool_path, cc_toolchain = cc_toolchain, emit = emit, feature_configuration = feature_configuration, diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl index f302bab743..37fb71c4d0 100644 --- a/rust/private/rustdoc.bzl +++ b/rust/private/rustdoc.bzl @@ -126,12 +126,19 @@ def rustdoc_compile_action( # arguments expecting to do so. rustdoc_crate_info = _strip_crate_info_output(crate_info) + if toolchain.sysroot_path: + rustdoc_tool_path = toolchain.sysroot_path + "/bin/rustdoc" + elif is_test: + rustdoc_tool_path = toolchain.rust_doc.short_path + else: + rustdoc_tool_path = toolchain.rust_doc.path + 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, + tool_path = rustdoc_tool_path, cc_toolchain = cc_toolchain, feature_configuration = feature_configuration, crate_info = rustdoc_crate_info, diff --git a/rust/private/system_rust_configure.bzl b/rust/private/system_rust_configure.bzl new file mode 100644 index 0000000000..20d6554ce6 --- /dev/null +++ b/rust/private/system_rust_configure.bzl @@ -0,0 +1,376 @@ +"""Repository rule for configuring a system-installed Rust toolchain. + +Follows the `cc_configure` pattern from rules_cc: probes the system at fetch +time and generates a BUILD file with `rust_toolchain()` targets that reference +the host-installed compiler and standard library without including sysroot files +as action inputs. + +This is useful for remote-execution setups where every worker already has an +identical Rust toolchain installed (e.g. via a container image or system +package). By pointing at the system toolchain, ~670 MB of sysroot files are +excluded from action input sets, eliminating upload/transfer overhead. +""" + +load("//rust/platform:triple_mappings.bzl", "triple_to_constraint_set") + +def _fail_if_not_found(repository_ctx, result, tool): + """Fail with a helpful message when a required tool is missing.""" + if result.return_code != 0: + fail( + "Failed to run '{}': exit code {}\nstdout: {}\nstderr: {}".format( + tool, + result.return_code, + result.stdout, + result.stderr, + ), + ) + +def _rust_system_toolchain_repository_impl(repository_ctx): + """Probes the system Rust toolchain and generates BUILD targets. + + The generated BUILD file contains: + - A `rust_stdlib_filegroup` with an empty srcs (sysroot files are not + tracked as action inputs). + - A `rust_toolchain` target with `sysroot_path` set, which causes + rules_rust to pass `--sysroot=` to rustc and skip packaging + sysroot files into action inputs. + - A `toolchain()` registration target for each requested target triple. + """ + tool_path = repository_ctx.attr.tool_path + + # Locate rustc + if tool_path: + rustc = repository_ctx.path(tool_path + "/bin/rustc") + cargo = repository_ctx.path(tool_path + "/bin/cargo") + rustdoc = repository_ctx.path(tool_path + "/bin/rustdoc") + clippy_driver = repository_ctx.path(tool_path + "/bin/clippy-driver") + cargo_clippy = repository_ctx.path(tool_path + "/bin/cargo-clippy") + else: + rustc = repository_ctx.which("rustc") + if not rustc: + fail("Could not find `rustc` on PATH and no `tool_path` was provided.") + cargo = repository_ctx.which("cargo") + rustdoc = repository_ctx.which("rustdoc") + clippy_driver = repository_ctx.which("clippy-driver") + cargo_clippy = repository_ctx.which("cargo-clippy") + + # Probe the sysroot + result = repository_ctx.execute([str(rustc), "--print", "sysroot"]) + _fail_if_not_found(repository_ctx, result, "rustc --print sysroot") + sysroot = result.stdout.strip() + + # Probe the version + result = repository_ctx.execute([str(rustc), "--version", "--verbose"]) + _fail_if_not_found(repository_ctx, result, "rustc --version --verbose") + version_output = result.stdout.strip() + + # Parse version (first line is like "rustc 1.86.0-nightly (abc123 2026-01-29)") + version_line = version_output.split("\n")[0] + rustc_version = version_line.split(" ")[1].split("-")[0] if " " in version_line else "" + + # Parse host triple from verbose output + host_triple = "" + for line in version_output.split("\n"): + if line.startswith("host:"): + host_triple = line.split(":", 1)[1].strip() + break + + # Validate version if user specified one + if repository_ctx.attr.version: + if not version_line.split(" ")[1].startswith(repository_ctx.attr.version) if " " in version_line else True: + # buildifier: disable=print + print("WARNING: Expected Rust version '{}' but found '{}'".format( + repository_ctx.attr.version, + version_line, + )) + + # Determine exec_triple + exec_triple = repository_ctx.attr.exec_triple if repository_ctx.attr.exec_triple else host_triple + if not exec_triple: + fail("Could not determine exec_triple. Set it explicitly or ensure `rustc --version --verbose` reports a host triple.") + + # Determine target triples + target_triples = repository_ctx.attr.target_triples if repository_ctx.attr.target_triples else [exec_triple] + + # For each target triple, probe the target libdir to confirm it exists + for target in target_triples: + result = repository_ctx.execute([str(rustc), "--print", "target-libdir", "--target", target]) + if result.return_code != 0: + # buildifier: disable=print + print("WARNING: rustc cannot find target-libdir for '{}': {}".format(target, result.stderr.strip())) + + # Determine system extension values based on exec triple + binary_ext, staticlib_ext, dylib_ext = _extensions_for_triple(exec_triple) + + # Create symlinks to system binaries so that Bazel can track them as labels. + # Using symlinks avoids copying the binaries and keeps them in-tree for the + # rule to reference. + repository_ctx.symlink(rustc, "bin/rustc") + if cargo: + repository_ctx.symlink(cargo, "bin/cargo") + if rustdoc: + repository_ctx.symlink(rustdoc, "bin/rustdoc") + if clippy_driver: + repository_ctx.symlink(clippy_driver, "bin/clippy-driver") + if cargo_clippy: + repository_ctx.symlink(cargo_clippy, "bin/cargo-clippy") + + # Determine stdlib linkflags per target triple + # For now we use a simplified approach -- the full mapping is in + # triple_mappings.bzl but we don't have access to the `triple()` struct + # at repository-rule time. We infer from the triple string. + stdlib_linkflags_map = {} + for target in target_triples: + stdlib_linkflags_map[target] = _stdlib_linkflags_for_triple(target) + + # Build the BUILD.bazel content + build_content = _generate_build_file( + exec_triple = exec_triple, + target_triples = target_triples, + sysroot = sysroot, + has_cargo = cargo != None, + has_clippy = clippy_driver != None, + has_cargo_clippy = cargo_clippy != None, + binary_ext = binary_ext, + staticlib_ext = staticlib_ext, + dylib_ext = dylib_ext, + default_edition = repository_ctx.attr.default_edition, + stdlib_linkflags_map = stdlib_linkflags_map, + extra_rustc_flags = repository_ctx.attr.extra_rustc_flags, + extra_exec_rustc_flags = repository_ctx.attr.extra_exec_rustc_flags, + ) + + repository_ctx.file("WORKSPACE.bazel", 'workspace(name = "{}")\n'.format(repository_ctx.name)) + repository_ctx.file("BUILD.bazel", build_content) + + # Generate bin/BUILD.bazel that exports the tool symlinks as labels. + bin_exports = ["rustc", "rustdoc"] + if cargo: + bin_exports.append("cargo") + if clippy_driver: + bin_exports.append("clippy-driver") + if cargo_clippy: + bin_exports.append("cargo-clippy") + bin_build = 'exports_files([{}])\n'.format( + ", ".join(['"{}"'.format(f) for f in bin_exports]), + ) + repository_ctx.file("bin/BUILD.bazel", bin_build) + +def _extensions_for_triple(triple_str): + """Returns (binary_ext, staticlib_ext, dylib_ext) for a platform triple.""" + if "windows" in triple_str: + return (".exe", ".lib", ".dll") + elif "darwin" in triple_str or "apple" in triple_str: + return ("", ".a", ".dylib") + else: + return ("", ".a", ".so") + +def _stdlib_linkflags_for_triple(triple_str): + """Returns stdlib linkflags as a list of strings for a given triple.""" + if "windows" in triple_str and "msvc" in triple_str: + return ["-ladvapi32", "-lws2_32", "-luserenv", "-lbcrypt", "-lntdll", "-lsynchronization"] + elif "windows" in triple_str and "gnu" in triple_str: + return ["-ladvapi32", "-lws2_32", "-luserenv", "-lbcrypt", "-lntdll", "-lsynchronization"] + elif "darwin" in triple_str or "apple" in triple_str: + return ["-lc", "-lm", "-ldl", "-framework", "Security", "-framework", "CoreFoundation"] + elif "freebsd" in triple_str: + return ["-lc", "-lm", "-lpthread", "-lexecinfo"] + elif "fuchsia" in triple_str: + return ["-lc", "-lm", "-lzircon", "-lfdio"] + elif "wasm" in triple_str: + return [] + else: + # Linux and similar + return ["-lc", "-lm", "-ldl", "-lpthread"] + +def _generate_build_file( + exec_triple, + target_triples, + sysroot, + has_cargo, + has_clippy, + has_cargo_clippy, + binary_ext, + staticlib_ext, + dylib_ext, + default_edition, + stdlib_linkflags_map, + extra_rustc_flags, + extra_exec_rustc_flags): + """Generates the BUILD.bazel content for the system toolchain repository.""" + lines = [] + lines.append('load("@rules_rust//rust:toolchain.bzl", "rust_toolchain", "rust_stdlib_filegroup")') + lines.append("") + + # For each target triple, generate a rust_stdlib_filegroup + rust_toolchain + for target in target_triples: + stdlib_name = "rust_std-{}".format(target) + toolchain_name = "rust_toolchain-{}".format(target) + proxy_name = "toolchain-{}".format(target) + + stdlib_linkflags = stdlib_linkflags_map.get(target, []) + linkflags_str = ", ".join(['"{}"'.format(f) for f in stdlib_linkflags]) + + # Empty stdlib filegroup -- the sysroot files live on the system and + # are not tracked as Bazel action inputs. + lines.append('rust_stdlib_filegroup(') + lines.append(' name = "{}",'.format(stdlib_name)) + lines.append(" srcs = [],") + lines.append(' visibility = ["//visibility:public"],') + lines.append(")") + lines.append("") + + lines.append("rust_toolchain(") + lines.append(' name = "{}",'.format(toolchain_name)) + lines.append(' rustc = "//bin:rustc",') + lines.append(' rust_doc = "//bin:rustdoc",') + lines.append(' rust_std = ":{}",'.format(stdlib_name)) + if has_cargo: + lines.append(' cargo = "//bin:cargo",') + if has_clippy: + lines.append(' clippy_driver = "//bin:clippy-driver",') + if has_cargo_clippy: + lines.append(' cargo_clippy = "//bin:cargo-clippy",') + lines.append(' binary_ext = "{}",'.format(binary_ext)) + lines.append(' staticlib_ext = "{}",'.format(staticlib_ext)) + lines.append(' dylib_ext = "{}",'.format(dylib_ext)) + lines.append(' stdlib_linkflags = [{}],'.format(linkflags_str)) + lines.append(' exec_triple = "{}",'.format(exec_triple)) + lines.append(' target_triple = "{}",'.format(target)) + lines.append(' sysroot_path = "{}",'.format(sysroot)) + if default_edition: + lines.append(' default_edition = "{}",'.format(default_edition)) + if extra_rustc_flags: + lines.append(" extra_rustc_flags = {},".format(repr(extra_rustc_flags))) + if extra_exec_rustc_flags: + lines.append(" extra_exec_rustc_flags = {},".format(repr(extra_exec_rustc_flags))) + lines.append(' visibility = ["//visibility:public"],') + lines.append(")") + lines.append("") + + # Toolchain registration target + exec_constraints = triple_to_constraint_set(exec_triple) + target_constraints = triple_to_constraint_set(target) + lines.append("toolchain(") + lines.append(' name = "{}",'.format(proxy_name)) + lines.append(' toolchain = ":{}",'.format(toolchain_name)) + lines.append(' toolchain_type = "@rules_rust//rust:toolchain",') + lines.append(" exec_compatible_with = {},".format(repr(exec_constraints))) + lines.append(" target_compatible_with = {},".format(repr(target_constraints))) + lines.append(' visibility = ["//visibility:public"],') + lines.append(")") + lines.append("") + + return "\n".join(lines) + +rust_system_toolchain_repository = repository_rule( + doc = """\ +Probes a system-installed Rust toolchain and generates `rust_toolchain()` targets. + +This follows the `cc_configure` pattern from rules_cc: the repository rule runs +at fetch time, probes the local system for `rustc`, and generates a BUILD file +with toolchain targets that reference the system binaries via symlinks. + +When these toolchains are used, the sysroot files (stdlib, rustc libs) are NOT +included as Bazel action inputs. Instead, `--sysroot=` is passed to rustc +so it finds them at their system location. This is ideal for remote execution +environments where workers have a matching Rust toolchain pre-installed. + +Example usage in WORKSPACE: +```starlark +load("@rules_rust//rust/private:system_rust_configure.bzl", "rust_system_toolchain_repository") + +rust_system_toolchain_repository( + name = "system_rust", + default_edition = "2021", +) + +register_toolchains("@system_rust//:toolchain-x86_64-unknown-linux-gnu") +``` + +Example usage in MODULE.bazel via the module extension: +```starlark +system_rust = use_extension("@rules_rust//rust/private:system_rust_configure.bzl", "system_rust_ext") +system_rust.toolchain( + name = "system_rust", + default_edition = "2021", +) +use_repo(system_rust, "system_rust") +register_toolchains("@system_rust//:toolchain-x86_64-unknown-linux-gnu") +``` +""", + implementation = _rust_system_toolchain_repository_impl, + attrs = { + "default_edition": attr.string( + doc = "The default Rust edition (e.g. '2021', '2024'). If not set, each target must specify its edition.", + ), + "exec_triple": attr.string( + doc = ( + "The Rust-style execution platform triple (e.g. 'x86_64-unknown-linux-gnu'). " + + "If not set, auto-detected from `rustc --version --verbose`." + ), + ), + "extra_exec_rustc_flags": attr.string_list( + doc = "Extra flags to pass to rustc in exec configuration.", + ), + "extra_rustc_flags": attr.string_list( + doc = "Extra flags to pass to rustc in non-exec configuration.", + ), + "target_triples": attr.string_list( + doc = ( + "List of Rust-style target triples to generate toolchains for " + + "(e.g. ['x86_64-unknown-linux-gnu', 'aarch64-unknown-linux-gnu']). " + + "If not set, defaults to the exec_triple (native compilation only)." + ), + ), + "tool_path": attr.string( + doc = ( + "Absolute path to the Rust toolchain directory (e.g. a rustup toolchain " + + "directory like '/home/user/.rustup/toolchains/nightly-2026-01-29-x86_64-unknown-linux-gnu'). " + + "If not set, tools are located via PATH." + ), + ), + "version": attr.string( + doc = "Expected Rust version (e.g. '1.86.0'). If set, a warning is emitted when the system version does not match.", + ), + }, + environ = ["PATH", "RUSTUP_HOME", "RUSTUP_TOOLCHAIN", "CARGO_HOME"], + local = True, +) + +def _system_rust_ext_impl(module_ctx): + """Module extension implementation for system Rust toolchains.""" + for mod in module_ctx.modules: + for toolchain in mod.tags.toolchain: + rust_system_toolchain_repository( + name = toolchain.name, + version = toolchain.version, + exec_triple = toolchain.exec_triple, + target_triples = toolchain.target_triples, + tool_path = toolchain.tool_path, + default_edition = toolchain.default_edition, + extra_rustc_flags = toolchain.extra_rustc_flags, + extra_exec_rustc_flags = toolchain.extra_exec_rustc_flags, + ) + +_toolchain_tag = tag_class( + doc = "Declares a system Rust toolchain to be probed and registered.", + attrs = { + "default_edition": attr.string(doc = "Default Rust edition."), + "exec_triple": attr.string(doc = "Execution platform triple."), + "extra_exec_rustc_flags": attr.string_list(doc = "Extra flags for rustc in exec configuration."), + "extra_rustc_flags": attr.string_list(doc = "Extra flags for rustc in non-exec configuration."), + "name": attr.string(doc = "Repository name.", mandatory = True), + "target_triples": attr.string_list(doc = "Target triples to generate toolchains for."), + "tool_path": attr.string(doc = "Absolute path to the Rust toolchain directory."), + "version": attr.string(doc = "Expected Rust version for validation."), + }, +) + +system_rust_ext = module_extension( + doc = "Module extension for configuring system-installed Rust toolchains.", + implementation = _system_rust_ext_impl, + tag_classes = { + "toolchain": _toolchain_tag, + }, +) diff --git a/rust/private/unpretty.bzl b/rust/private/unpretty.bzl index be111c83a3..ff7214c359 100644 --- a/rust/private/unpretty.bzl +++ b/rust/private/unpretty.bzl @@ -185,12 +185,14 @@ def _rust_unpretty_aspect_impl(target, ctx): if crate_info.is_test: rust_flags = get_rust_test_flags(ctx.rule.attr) + rustc_tool_path = (toolchain.sysroot_path + "/bin/rustc") if toolchain.sysroot_path else toolchain.rustc.path + args, env = construct_arguments( ctx = ctx, attr = ctx.rule.attr, file = ctx.file, toolchain = toolchain, - tool_path = toolchain.rustc.path, + tool_path = rustc_tool_path, cc_toolchain = cc_toolchain, feature_configuration = feature_configuration, crate_info = crate_info, diff --git a/rust/toolchain.bzl b/rust/toolchain.bzl index f59f7ac853..ec3d65966c 100644 --- a/rust/toolchain.bzl +++ b/rust/toolchain.bzl @@ -425,28 +425,43 @@ def _rust_toolchain_impl(ctx): linker = ctx.attr.linker, ) - # Determine the path and short_path of the sysroot - sysroot_path = sysroot.sysroot_anchor.dirname - sysroot_short_path, _, _ = sysroot.sysroot_anchor.short_path.rpartition("/") + # Determine the path and short_path of the sysroot. + # When sysroot_path is set, use the system sysroot directly instead of the + # generated one. This avoids including sysroot files as action inputs. + if ctx.attr.sysroot_path: + sysroot_path = ctx.attr.sysroot_path + sysroot_short_path = ctx.attr.sysroot_path + else: + sysroot_path = sysroot.sysroot_anchor.dirname + sysroot_short_path, _, _ = sysroot.sysroot_anchor.short_path.rpartition("/") + + # When sysroot_path is set, RUSTC/RUSTDOC should point to the system binaries + # rather than the generated sysroot symlinks. + if ctx.attr.sysroot_path: + rustc_path_str = ctx.attr.sysroot_path + "/bin/rustc" + rustdoc_path_str = ctx.attr.sysroot_path + "/bin/rustdoc" + else: + rustc_path_str = sysroot.rustc.path + rustdoc_path_str = sysroot.rustdoc.path # Variables for make variable expansion make_variables = { - "RUSTC": sysroot.rustc.path, - "RUSTDOC": sysroot.rustdoc.path, + "RUSTC": rustc_path_str, + "RUSTDOC": rustdoc_path_str, "RUST_DEFAULT_EDITION": ctx.attr.default_edition or "", "RUST_SYSROOT": sysroot_path, "RUST_SYSROOT_SHORT": sysroot_short_path, } - if sysroot.cargo: - make_variables.update({ - "CARGO": sysroot.cargo.path, - }) + if ctx.attr.sysroot_path: + make_variables.update({"CARGO": ctx.attr.sysroot_path + "/bin/cargo"}) + elif sysroot.cargo: + make_variables.update({"CARGO": sysroot.cargo.path}) - if sysroot.rustfmt: - make_variables.update({ - "RUSTFMT": sysroot.rustfmt.path, - }) + if ctx.attr.sysroot_path: + make_variables.update({"RUSTFMT": ctx.attr.sysroot_path + "/bin/rustfmt"}) + elif sysroot.rustfmt: + make_variables.update({"RUSTFMT": sysroot.rustfmt.path}) make_variable_info = platform_common.TemplateVariableInfo(make_variables) @@ -562,10 +577,17 @@ def _rust_toolchain_impl(ctx): std, ) - # Include C++ toolchain files to ensure tools like 'ar' are available for cross-compilation - all_files_depsets = [sysroot.all_files] - if cc_toolchain and cc_toolchain.all_files: - all_files_depsets.append(cc_toolchain.all_files) + # Include C++ toolchain files to ensure tools like 'ar' are available for cross-compilation. + # When sysroot_path is set, exclude Rust sysroot files from action inputs since the + # execution environment has the toolchain pre-installed at the given path. + if ctx.attr.sysroot_path: + all_files_depsets = [] + if cc_toolchain and cc_toolchain.all_files: + all_files_depsets.append(cc_toolchain.all_files) + else: + all_files_depsets = [sysroot.all_files] + if cc_toolchain and cc_toolchain.all_files: + all_files_depsets.append(cc_toolchain.all_files) toolchain = platform_common.ToolchainInfo( all_files = depset(transitive = all_files_depsets), @@ -605,6 +627,7 @@ def _rust_toolchain_impl(ctx): per_crate_rustc_flags = ctx.attr.per_crate_rustc_flags, sysroot = sysroot_path, sysroot_short_path = sysroot_short_path, + sysroot_path = ctx.attr.sysroot_path, target_arch = target_arch, target_flag_value = target_json.path if target_json else target_triple.str, target_json = target_json, @@ -831,6 +854,17 @@ rust_toolchain = rule( "opt": "debuginfo", }, ), + "sysroot_path": attr.string( + doc = ( + "Absolute path to a system-installed Rust sysroot. When set, this path is " + + "passed as `--sysroot` to rustc and sysroot files are excluded from action " + + "inputs. This eliminates large toolchain file transfers when using remote " + + "execution with workers that have the Rust toolchain pre-installed. Typically " + + "set by the `rust_system_toolchain_repository` repository rule rather than " + + "manually." + ), + default = "", + ), "target_json": attr.string( doc = ("Override the target_triple with a custom target specification. " + "For more details see: https://doc.rust-lang.org/rustc/targets/custom.html"),