Squashed 'third_party/rules_rust/' changes from 078c6908f..bf9ddeb7c

bf9ddeb7c Release 0.25.1 (#2049)
db5b2fd65 Update tinyjson (#2050)
6a7872ae3 Fix prost proto packages not sanitizing to valid module names (#2044)
c080d7bfa Moved legacy protobuf rules to `proto/protobuf` (#2043)
1281cc051 Remove debug code. (#2048)
cd126be1f Fix build failure finding crate_roots when mixed with generated sources (#2041)
7f751cddd Consolidate rust_prost_library and fix extension-only proto generation. (#2047)
6118c81f2 Release 0.25.0 (#2042)
a6f29fd07 Add Prost and Tonic rules. (#2033)
9442aed8c fix: `crate_type` more accurately corresponds to CC linking actions (#1975)
4f4e2b17b Re-enable zig example on CI (#2030)
2ded0c2f5 Fix flaky coverage test in CI (#2028)
36f8251f9 Exclude .tmp_git_root from globs (#1948)
ca750fa83 Eliminate Rustfmt action in Bindgen rules. Bindgen can run rustfmt (#2025)
c55ec0cfb Allow sysroots from cc_toolchains to be added to bindgen actions (#2024)
9314b1b0c Release 0.24.1 (#2023)
92ea74ade Making rust_std attr in rust_toolchain mandatory (#1984)
a54b8e14b Update `rust_library_group` to use `DepVariantInfo` (#2022)
47644346b Release v0.24.0 (#2020)
a6b0a7f39 Rust library group (#1848)
bc43f4841 Fix crate_universe's `all_crate_deps` and `aliases` functions failing in case the crate's Cargo.toml has condtional dependencies (#2018)
8f27ec7c5 fix: load cargo manifest without resolving abs path of deps (#2017)
23f99bb63 feature: `target_compatible_with` added to `CommonAttrs` (#1976)
11f8c9875 Make `rust_doc_test` inherit it's crate aliases attribute (#2007)
8e848414d Regenerated crate_universe outputs for all packages (#2011)
1b6365131 Don't use startup:windows (#2012)
e80582e75 Fix thumbv* platform resolution (#2010)
367f90ef0 Update bindgen version to 0.65.1 (#2008)
e6ed5bf90 Release 0.23.0 (#2003)
93b230bb8 Fix code coverage collection. (#2001)
0a14bfbb0 Minor CI and test cleanup (#2004)
3e2ee941a Update bindgen rules to build clang from source. (#1998)
5a1a7577d Split up cargo_build_script tests (#2002)
eb6413e83 Update various bash scripts to pipe errors to stderr (#1999)
affe947ac Update stardoc version (#1997)
7073146f8 Add support for armv8-m (#1993)
73a06f130 Added Rust 1.70.0 (#1991)
23c20a93f Fixes crates_vendor workspace name detection when using bzlmod (#1990)
f5813fa08 Set windows flags in platform-specific bazelrc (#1988)
c1632b5b5 Fix up anchor link (#1987)
56e760487 Fix typo in crate_universe-generated defs.bzl comment (#1981)
94cbe4c2c Symlink in the exec-root so that relative paths will work, unchanged. (#1781)
af8ef62eb Release 0.22.0 (#1974)
4aaa6de30 Allow specifying exec and target compatibility constraints (#1971)
f1b19c394 Update rules_apple in tests (#1972)
937e63399 Add T2 support for x86_64-unknown-none (#1967)
66b1bf165 fix: lld-link (MSVC) fix flags including `-l` prefix (#1958)
285dcbbb9 feature: expose `extra_rustc_flags` and `extra_exec_rustc_flags` at `rust_register_toolchains` (#1959)
0f25cb462 Removed `rust_toolchain.os` in favor of `rust_toolchain.exec_triple`. (#1960)
a2a1109dc Add T2 support for thumbv7em-none-eabi (#1957)
80f0eb488 Support for `no_std` mode (#1934)
99aaf0830 Rename crates_vendor_manifests to cvm to shorten windows path lengths (#1944)
0a57da049 Added tests for build script dependencies to crate_universe (#1943)
caffb0a08 Release 0.21.1 (#1936)
c869a17c7 Fix regression in building zlib (#1935)
24b9dea4f Release 0.21.0 (#1933)
7677c617e Add support for rustc flags to `rust_proto_library` (#1932)
fa304ae48 Updated zlib BUILD file to support darwin-arm64 (#1931)
a86313282 Added Rust 1.69.0 (#1930)
f0e12c707 Make BuildInfo provider public (#1920)
c6ad23aba Respect `#[global_allocator]` in `cc_common.link` builds (#1926)
d78752504 Exclude target directory from release tars (#1922)
0339cd18a [wasm-bindgen] Update to v0.2.84 (#1919)
07af5678e Handle corner case for windows architecture detection (#1915)
c56e7660d Fix optional deps by platform (#1911)
4663ff6a3 cc_common_link: also respect --custom_malloc if set (#1912)
dab425760 Add Rust 1.68.2 (#1908)
e4bd39f95 Add empty rustfmt.toml (#1907)
eaf513865 Support bzlmod (#1528)
1074ecbab Release v0.20.0 (#1900)
44aec0a79 ci: fix test config in cc_common_link_ubuntu2004 (#1904)
6571cde64 Adds per_crate_rustc_flag build setting. (#1827)
7a47449df Added Rust 1.68.1 (#1898)
e3bcdbad1 Fixed rustdoc warnings in crate_universe (#1897)
529f45900 Added `rustdoc_flags` attribute to rust_doc rule (#1867)
9e3499405 Have rustdoc return its output directory instead of zip default. (#1868)
9d6741f40 Implement support for optional crates enabled with dep: features (#1885)
fd10963ea Skip adding -lstatic to libtest and libstd on Darwin (#1620)
b3314b45e Release 0.19.1 (#1895)
c1a9fd86f Accumulate all features from cargo tree output (#1884)
206f71c95 Disable zig example (#1893)
1a5d07cd2 Add runfiles support to rust_stdlib_filegroup (#1890)
6996cd550 Deleted unused targets and cleanup docs (#1889)
a85e24e20 Fix triple constraints for iOS and watchOS (#1888)
e13fd3bad Release rules_rust and cargo-bazel (#1882)
9e9853d63 Add support for thumbv7em with hard float (#1871)
b3cd5962e Added Rust 1.68.0 (#1866)
f1b7aedf5 Support sparse indexes (#1857)
7f2dd433a Make fetch_shas work with mktemp from coreutils 8.32 (#1870)
a9cc01230 Update crate_universe dependencies (#1872)
c038e94ae Pipe stderr from cargo tree processes (#1879)
222d60322 Parallelize cargo tree calls (#1874)
cdbbf7131 Add Fuchsia platform support (#1833)
17e5b04c2 Use `_make_link_flags_darwin` when target os is `ios`. (#1843)
d9ecc8df4 crate_universe: Support fetching crates with git branch, tag or rev (#1846)
1c694cd60 Forward `toolchains` to `cargo_build_script` targets (#1862)
9affcbfa7 Skip detecting abi for empty values (#1830)
6193fe823 Re-enable crate_universe MacOS tests (#1861)
c25db95ae Updated Rust to 1.67.1 (#1864)
7b8fd06be Support `[patch]` in crate_universe when using multiple `Cargo.toml`s (#1856)
c645fe399 Silence windows build failure (#1863)
75bba7b50 Make rust_clippy providers match rustfmt_test (#1806)
f09d72755 Fix test assertion for arm64 macs (#1845)
f4113bfe1 Fix tests for new Apple toolchain (#1844)
20ce44e30 fix: use target_triple struct instead of string (#1835)
bdbded181 Fix code example in doc (#1838)
4f4014052 Fix typo: plced -> placed (#1834)
baeb0664d Remove ios/android/wasm support for gen_rust_project deps (#1684)
02557a47a Add `render_config` attribute to `crates_vendor`. (#1832)
4357fb154 Updated rules_rust to version 0.18.0 (#1829)
9adfdca9b Various cleanups (#1828)
4fa412385 Added update known shas to include T1-T2 triples (#1824)
905731ad9 Instructions on how to perform `rustfmt` check (#1822) (#1823)
108b1a187 Encapsulate running cargo into a struct (#1815)
57a099b63 Fixes resolver issue with root packages and another dependency format (#1819)
78ca9ba0a Use env method recently added to cargo_metadata (#1813)
92834930f Updated `rust_toolchain.target_json` to take encoded json strings (#1810)
84f1d0657 support `resolver = "2"` target-specific features (#1710)
a5853fd37 Use correct dynamic link args fro proc-macro crates (#1803)
b656e2553 Added tests for the `triple` constructor (#1811)
ea4a79ad9 Disable job in CI to avoid infrastructure failure. (#1816)
2fc02f030 Delete `rust_toolchain.rusrc_srcs` (#1807)
804d5fc1f Convert `rust_toolchain` attrs `exec_triple` and `target_triple` to structs (#1808)
499a2ca38 Updated platform triple values from strings to structs ("triple") (#1804)
aae1dbdcb Unify functions for computing constraint values for platform triple abi (#1805)
0d6d2b1eb Updated rules_rust version to `0.17.0` (#1800)
88e83f2df Added Rust 1.67.0 (#1799)
6922b5012 rustdoc_test: fix and test OUT_DIR (#1779)
ad01d1b0e [crate_universe] add an annotation to disable pipelining (#1733)
f651cd18f Add `CARGO_BAZEL_REPIN_ONLY` repinning allowlist (#1798)
d7f0debb0 Revert "Disable broken clang and ldd CI jobs (#1785)" (#1796)
96f82aaad Fix `cc_common.link` file output name (#1795)
5079b64d5 Fix use of `rustfmt_toolchain` when `rustc` is not provided (#1794)
23c650f35 Have `--experimental_use_cc_common_link` cover `rust_shared_library` (#1792)
ba0fb5956 Added support for `--nolegacy_external_runfiles` to `rust_doc_test` (#1790)
112242bb7 Prevent crates_vendor from restarting bazel. (#1791)
52231ef9f Added compatibility flags to `.bazelrc` to prevent regressions (#1789)
91cd399a0 Add "crate-name={}" tag to Crate Universe targets (#1787)
1b1dae196 Added Rust 1.66.1 (#1767)
fe17e8b8e Add file:// prefix to env var in docs (#1788)
0fe742bff Updated `rust_bindgen` to use `rustfmt_toolchain` (#1770)
042fd6c1c Update docs on setting Rust versions (#1786)
dddd8a0d4 Updated crate_universe dependencies (#1775)
a1330a71f Download `rustc` in `rustfmt_toolchain_repository` (#1769)
e96aad9aa Updated the ios_build example to use `crates_vendor` (#1778)
e315007df Disable broken clang and ldd CI jobs (#1785)
4e89d52a9 rustdoc_test: substitute the root of the current crate (#1777)
a52041fb5 Support `target_settings` in `rust_repository_set` and `rust_toolchain_repository` (#1758)
49906eb29 Update clippy and rustfmt aspects to require CrateInfo providers (#1772)
85564208e Updated rules_rust version to `0.16.1` (#1761)
614499a5b Fixed inability to deserialize crate_universe lockfiles (#1760)
9803d3034 Fix data and compile_data for rust_doc (#1741)
927a364cb Update Release github pipeline to trigger automatically (#1757)
7d03e05f8 Fix release pipeline (#1756)
cf7ca5dfd Updated rules_rust to version `0.16.0` (#1750)
203fe4b9a Remove unnecessary binary file (#1755)
941c7cca9 Don't propagate `compatible_with` to the underlying `cargo_build_script` `rust_binary` target (#1754)
a31490d9a Make loads from @rules_rust//rust:defs.bzl come out on one line (#1753)
7ebad4d50 Generate only the needed subset of binaries for bindgen and proto (#1751)
4ef3d4aaa Repin examples/crate_universe_unnamed (#1752)
d6e300359 Regenerate BUILD files using serde_starlark renderer (#1746)
e7c8a97d1 Convert BUILD.$name-$version.bazel to serde_starlark (#1743)
c09818d3b Exclude generated files from language stats and collapse in code review (#1747)
26a24f030 Added CI for single toolchain channel workspaces (#1712)
caed7d814 Report context on error failing to get version (#1744)
36b57af7b Add gen_binaries annotation to control which bins to make target for (#1718)
d916a6f52 crate_universe re-pinning now defaults to "workspace" (#1723)
f34661ee1 Propagate `compatible_with` attribute to the underlying `_build_script_run` target (#1745)
92977d1bf Re-pinned all dependencies managed by crate_universe (#1735)
d5289ad1c Added `rustfmt_toolchain` and refactored toolchain repository rules (#1719)
532e60ff0 Collect targets in a deterministic order (#1736)
52e02c25b Eliminate all use of hash-based collections from crate_universe (#1737)
31073ff8e Replace tera template with serde_starlark (#1734)
d4e5586d0 Support the RUNFILES_DIR environment variable. (#1732)
1357b85b1 Addressed clippy warnings from `clippy 0.1.67 (ec56537c 2022-12-15)` (#1717)
8bc9f788d Support dsym_folder output group in tests (#1703)
90c5b6eb7 Added CI for minimum supported Rust version (#1720)
be82ff8bd Match prerelease versions with annotation wildcard (#1716)
36c7f285b Arm Thumb Embedded Targets. (#1721)
5ef52e465 Update current_toolchain_files tests to use a dedicated test rule (#1714)
c75ea6f9e Add `Runfiles::current_repository` to runfiles library (#1713)
2f0511782 Updated rules_rust to version `0.15.0` (#1706)
019f87178 Added Rust 1.66.0 (#1705)
1469cd7cb Fix labels to work with canonical label literals. (#1700)
5826a500a Add riscv32imc and riscv64gc to the known sha targets (#1698)
40dee95ce Fixed typos: normla -> normal (#1699)
8f08e77ac load_arbitrary_tool uses tool_suburl to look up sha256 (#1695)
8faec3060 Fix typos in crate_universe rendered comments (#1691)
bd64711ff Silence flaky test (#1693)
46b7ea5af Added a build setting for toolchain channels (#1671)
70b272aad Updated rules_rust to version `0.14.0` (#1669)
91e597dd1 Updated all crates_vendor outputs (#1687)
9a047b0b9 Updated crate_universe dependencies (#1686)
3a91d2f5b Add RV64GC target (#1683)
d9e752ab4 Add per-toolchain `rustc_flags` (#1635)
56237415e stardoc: Use backtick not `<code>` for attr default values  (#1682)
d4b31a494 Allow passing a bazel path to vendor explicitly (#1661)
d51bf9ce0 Updated crate_universe to work with `--nolegacy_external_runfiles` (#1680)
7f40636d1 crate_universe/private/crates_vendor.bzl typo fix (#1678)
025bf7db8 Merge cc toolchain flags into build script env (#1675)
b7c36c051 Fix confusing/misleading crate_universe docs (#1677)
29233e354 Revert #1564 (#1663)
ed32b6de2 Common glob excludes (#1673)
61b99cdd1 fix: add space to crate data exclude list (#1665)
8bb25b8b7 Support Windows ARM64 (aarch64-pc-windows-msvc) (#1664)
ddf2a4c23 Re-render crate BUILD files after #1647 (#1655)
44c7e1588 Group deps and aliases by platform triple rather than by cfg string when generating BUILD files. This avoid bazel errors due to duplicate keys/deps. (#1647)
de18d8bb6 Allow `buildifier` attribute to be a file (#1660)
aa0815dc9 Fix naming of ambiguous libs (#1625)
ff314d4ab Also pass -c opt to tests in opt mode CI (#1626)
ff4e90515 Reenable windows job (#1658)
c45b8e91f Updated rules_rust to version `0.13.0` (#1644)
87d6b6c37 Update `//util/label` to support `+` in packages (#1654)
ab6959db5 fix: Fix issue with wasi-0.11.0+wasi-snapshot-preview1 (#1632)
28c090ed0 Replaced custom platform constraint values with aliases to `@platforms` (#1652)
dfbea4f52 Deprecated `rust_toolchain.rustc_srcs` (#1653)
fd1db4391 Remove deprecated attributes from rust_toolchain and cargo_bootstrap (#1651)
c8ab970c4 Generated rust-project.json files now include sysroot paths (#1641)
0a3e04cf9 Fix vendoring when not in a package (#1646)
aece1e37d Deduplicate expand_location targets in rust-project.json crate creation to avoid a bazel crash (#1639)
03a0b2483 [docs] Fixing typos in CargoConfig doc strings (#1638)
bd4fd2ac5 Upgraded cfg-expr dependency to 0.12.0. (#1636)
330554a13 Disable failing job in CI (#1640)
849f106e6 Consider compilation mode when choosing `pic`/`nopic` object files to link (#1624)
53491d719 Updated rules_rust to version `0.12.0` (#1630)
8e8843724 Remove empty glob (#1628)
1f621a944 Added Rust 1.65.0 (#1627)
c6af4d025 Add `-c opt` mode to CI (#1621)
95320cc8b process_wrapper: print line on error to parse message as json (#1566)
81eaccf39 Fixed CI breakage (#1619)
478fc3a57 Fix ambiguous native dependencies in `proc_macro`s and `staticlib`s (#1611)
9e3d8415e Build deps of _build_script_run in 'exec' mode (#1561)
ea031082b Fixed outdated docs (#1614)
a8c540e49 Restore support for old cargo_build_script load statements (#1613)
295b5ccc7 Renamed `_build_script_run` rule to `cargo_build_script` (#1612)
3778069ec Remove references to Google mirror in docs (#1607)
aad54ba29 Updated crate_universe dependencies (#1606)
c349df2a6 Remove Google mirror from Starlark snippet in release notes (#1604)
0493b998d Avoid rendering a mock root package when possible (#1596)
b04aa053c process_wrapper: Apply substitutions to param files (#1565)
b209b3e15 Updated rules_rust to version `0.11.0`. (#1587)
b1079453b Typo correction on doc (#1593)
ca5678266 Updated crate_universe dependencies (#1591)
a364d448f Fixes crates_vendor labels in remote mode when used from the root workspace (#1575)
1cc37c268 Expose the output directory from cargo_build_script (#1588)
7ffe0a555 Ignore non-utf8 text in build script output (#1583)
c5b38fe00 Merge runfiles from transitive dependencies of rust_test crate attr (#1487)
da3d522d5 Fix build scripts targeting the wrong architecture (#1564)
d288ed634 Add `out_dir` support in `cargo_dep_env` (#1571)
78d6c1b46 fix: incorrect rustfmt edition query (#1582)
48927127e Set CARGO_MANIFEST_DIR at runtime for tests (#1559)
76bd69033 Add an output group for the .rmeta (#1585)
352bfeb05 Cleanup deprecated code (#1577)
86dc561f9 Move crate_root_src to utils (#1570)
beb554eb9 update to wasm-bindgen v0.2.83 (#1567)
73fd1616b Export AbsoluteLabel functionality (#1568)
c57e7a399 Remap $PWD to empty string instead of "." (#1563)
f0cdcedc2 Added Rust 1.64.0 (#1562)
1d326554a Update docs to show release policies and support (#1560)
78c793a0a Fix markdown typo in rust_analyzer.md (#1553)
c13980fb6 Add iOS examples (#1546)
8a5e07e9f Update apple_support (#1549)
6dacd9803 Strip leading '@'s for labels in the splicing manifest (#1547)
f73d1d6fb use crate_info.deps in establish_cc_info (#1543)
4845af6c0 Add android example (#1545)
9570b7aa7 Remove -lgcc from Android builds (#1541)
cb9ca1b81 Fix crate_universe/private/srcs.bzl to work with repo mappings (#1540)
d1fc9accc Minor cleanup of CI pipelines (#1534)
2bb077b3b Updated rules_rust to version 0.10.0 (#1533)
b8751b860 add cc config info to dummy wasm32 cc toolchain (#1532)
f5ed797ee Updates rust_test to use main.rs as the root when use_libtest_harness is false (#1518)
cfcaf21d5 Preserve directory structure of source files when some are generated (#1526)
51c065841 migrating to rbe_preconfig and remove bazel_toolchains (#1524)
055abd402 Fix typo in an example of crates_repository rule (#1520)
8bfed1cd2 Added Rust 1.63.0 (#1512)
3a69ce09b Update wasm_bindgen to 0.2.82 (#1513)

git-subtree-dir: third_party/rules_rust
git-subtree-split: bf9ddeb7c83a9fe8a7d87c76134cdd8e16131b62
Signed-off-by: Adam Snaider <adsnaider@gmail.com>
Change-Id: Id9490c68d6221da66953a915a25042ef8b848505
diff --git a/cargo/bootstrap/bootstrap_installer.rs b/cargo/bootstrap/bootstrap_installer.rs
index 32cb137..67239b7 100644
--- a/cargo/bootstrap/bootstrap_installer.rs
+++ b/cargo/bootstrap/bootstrap_installer.rs
@@ -29,7 +29,7 @@
 
 fn main() {
     if let Err(err) = install() {
-        eprintln!("{:?}", err);
+        eprintln!("{err:?}");
         std::process::exit(1);
     };
 }
diff --git a/cargo/cargo_bootstrap.bzl b/cargo/cargo_bootstrap.bzl
index 509ea47..10a0cfa 100644
--- a/cargo/cargo_bootstrap.bzl
+++ b/cargo/cargo_bootstrap.bzl
@@ -178,26 +178,22 @@
     _detect_changes(repository_ctx)
 
     if repository_ctx.attr.version in ("beta", "nightly"):
-        version_str = "{}-{}".format(repository_ctx.attr.version, repository_ctx.attr.iso_date)
+        channel = repository_ctx.attr.version
+        version = repository_ctx.attr.iso_date
     else:
-        version_str = repository_ctx.attr.version
+        channel = "stable"
+        version = 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
+    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,
+        channel = channel,
+        version = version,
     )
 
     binary_name = repository_ctx.attr.binary or repository_ctx.name
@@ -271,20 +267,19 @@
             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."
+                "`{system}` (eg. 'darwin'), `{channel}` (eg. 'stable'), and `{tool}` (eg. 'rustc.exe') will be " +
+                "replaced in the string if present."
             ),
-            default = "@rust_{system}_{arch}__{triple}_tools//:bin/{tool}",
-        ),
-        "rust_toolchain_repository_template": attr.string(
-            doc = "**Deprecated**: Please use `rust_toolchain_cargo_template` and `rust_toolchain_rustc_template`",
+            default = "@rust_{system}_{arch}__{triple}__{channel}_tools//:bin/{tool}",
         ),
         "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."
+                "`{system}` (eg. 'darwin'), `{channel}` (eg. 'stable'), and `{tool}` (eg. 'rustc.exe') will be " +
+                "replaced in the string if present."
             ),
-            default = "@rust_{system}_{arch}__{triple}_tools//:bin/{tool}",
+            default = "@rust_{system}_{arch}__{triple}__{channel}_tools//: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",
diff --git a/cargo/cargo_build_script.bzl b/cargo/cargo_build_script.bzl
index 87ee88e..af21a91 100644
--- a/cargo/cargo_build_script.bzl
+++ b/cargo/cargo_build_script.bzl
@@ -1,498 +1,16 @@
-# buildifier: disable=module-docstring
-load("@bazel_skylib//lib:paths.bzl", "paths")
-load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "CPP_COMPILE_ACTION_NAME", "C_COMPILE_ACTION_NAME")
-load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
-load("//rust:defs.bzl", "rust_binary", "rust_common")
+"""Legacy load locations for Cargo build script rules
 
-# buildifier: disable=bzl-visibility
-load("//rust/private:providers.bzl", _DepInfo = "DepInfo")
+Instead, `defs.bzl` should be used.
+"""
 
-# 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_args_and_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:
-        tuple: A tuple of the following items:
-            - (sequence): A flattened C command line flags for given action.
-            - (sequence): A flattened CXX command line flags for given action.
-            - (dict): C environment variables to be set for given action.
-    """
-    compile_variables = cc_common.create_compile_variables(
-        feature_configuration = feature_configuration,
-        cc_toolchain = cc_toolchain,
-    )
-    cc_c_args = cc_common.get_memory_inefficient_command_line(
-        feature_configuration = feature_configuration,
-        action_name = C_COMPILE_ACTION_NAME,
-        variables = compile_variables,
-    )
-    cc_cxx_args = cc_common.get_memory_inefficient_command_line(
-        feature_configuration = feature_configuration,
-        action_name = CPP_COMPILE_ACTION_NAME,
-        variables = compile_variables,
-    )
-    cc_env = cc_common.get_environment_variables(
-        feature_configuration = feature_configuration,
-        action_name = C_COMPILE_ACTION_NAME,
-        variables = compile_variables,
-    )
-    return cc_c_args, cc_cxx_args, cc_env
-
-def _pwd_flags(args):
-    """Prefix execroot-relative paths of known arguments with ${pwd}.
-
-    Args:
-        args (list): List of tool arguments.
-
-    Returns:
-        list: The modified argument list.
-    """
-    res = []
-    for arg in args:
-        s, opt, path = arg.partition("--sysroot=")
-        if s == "" and not paths.is_absolute(path):
-            res.append("{}${{pwd}}/{}".format(opt, path))
-        else:
-            res.append(arg)
-    return res
-
-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,
-        "NUM_JOBS": "1",
-        "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(_pwd_flags(link_args))
-
-    # MSVC requires INCLUDE to be set
-    cc_c_args, cc_cxx_args, cc_env = get_cc_compile_args_and_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
-
-        # Populate CFLAGS and CXXFLAGS that cc-rs relies on when building from source, in particular
-        # to determine the deployment target when building for apple platforms (`macosx-version-min`
-        # for example, itself derived from the `macos_minimum_os` Bazel argument).
-        env["CFLAGS"] = " ".join(_pwd_flags(cc_c_args))
-        env["CXXFLAGS"] = " ".join(_pwd_flags(cc_cxx_args))
-
-    # 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"
-
-    # Add environment variables from the Rust toolchain.
-    env.update(toolchain.env)
-
-    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_type")),
-        "@bazel_tools//tools/cpp:toolchain_type",
-    ],
-    incompatible_use_toolchain_transition = True,
+load(
+    "//cargo/private:cargo_build_script.bzl",
+    _cargo_dep_env = "cargo_dep_env",
+)
+load(
+    "//cargo/private:cargo_build_script_wrapper.bzl",
+    _cargo_build_script = "cargo_build_script",
 )
 
-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
-
-def _cargo_dep_env_implementation(ctx):
-    empty_file = ctx.actions.declare_file(ctx.label.name + ".empty_file")
-    empty_dir = ctx.actions.declare_directory(ctx.label.name + ".empty_dir")
-    ctx.actions.write(
-        output = empty_file,
-        content = "",
-    )
-    ctx.actions.run(
-        outputs = [empty_dir],
-        executable = "true",
-    )
-    return [
-        DefaultInfo(files = depset(ctx.files.src)),
-        BuildInfo(
-            dep_env = empty_file,
-            flags = empty_file,
-            link_flags = empty_file,
-            link_search_paths = empty_file,
-            out_dir = empty_dir,
-            rustc_env = empty_file,
-        ),
-        _DepInfo(
-            dep_env = ctx.file.src,
-            direct_crates = depset(),
-            link_search_path_files = depset(),
-            transitive_build_infos = depset(),
-            transitive_crate_outputs = depset(),
-            transitive_crates = depset(),
-            transitive_noncrates = depset(),
-        ),
-    ]
-
-cargo_dep_env = rule(
-    implementation = _cargo_dep_env_implementation,
-    doc = (
-        "A rule for generating variables for dependent `cargo_build_script`s " +
-        "without a build script. This is useful for using Bazel rules instead " +
-        "of a build script, while also generating configuration information " +
-        "for build scripts which depend on this crate."
-    ),
-    attrs = {
-        "src": attr.label(
-            doc = dedent("""\
-                File containing additional environment variables to set for build scripts of direct dependencies.
-
-                This has the same effect as a `cargo_build_script` which prints
-                `cargo:VAR=VALUE` lines, but without requiring a build script.
-
-                This 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 (`\\\\`).
-            """),
-            allow_single_file = True,
-            mandatory = True,
-        ),
-    },
-)
+cargo_build_script = _cargo_build_script
+cargo_dep_env = _cargo_dep_env
diff --git a/cargo/cargo_build_script_runner/bin.rs b/cargo/cargo_build_script_runner/bin.rs
index 9e40fef..b7b78c2 100644
--- a/cargo/cargo_build_script_runner/bin.rs
+++ b/cargo/cargo_build_script_runner/bin.rs
@@ -32,7 +32,7 @@
     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 manifest_dir = exec_root.join(manifest_dir_env);
     let rustc = exec_root.join(&rustc_env);
     let Options {
         progname,
@@ -48,15 +48,36 @@
         input_dep_env_paths,
     } = parse_args()?;
 
-    let out_dir_abs = exec_root.join(&out_dir);
+    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));
 
+    if should_symlink_exec_root() {
+        // Symlink the execroot to the manifest_dir so that we can use relative paths in the arguments.
+        let exec_root_paths = std::fs::read_dir(&exec_root)
+            .map_err(|err| format!("Failed while listing exec root: {err:?}"))?;
+        for path in exec_root_paths {
+            let path = path
+                .map_err(|err| {
+                    format!("Failed while getting path from exec root listing: {err:?}")
+                })?
+                .path();
+
+            let file_name = path
+                .file_name()
+                .ok_or_else(|| "Failed while getting file name".to_string())?;
+            let link = manifest_dir.join(file_name);
+
+            symlink_if_not_exists(&path, &link)
+                .map_err(|err| format!("Failed to symlink {path:?} to {link:?}: {err}"))?;
+        }
+    }
+
     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));
+    let mut command = Command::new(exec_root.join(progname));
     command
         .current_dir(&manifest_dir)
         .envs(target_env_vars)
@@ -127,7 +148,7 @@
         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)
+                format!(" with exit code {exit_code}")
             } else {
                 String::new()
             },
@@ -174,6 +195,42 @@
     Ok(())
 }
 
+fn should_symlink_exec_root() -> bool {
+    env::var("RULES_RUST_SYMLINK_EXEC_ROOT")
+        .map(|s| s == "1")
+        .unwrap_or(false)
+}
+
+/// Create a symlink from `link` to `original` if `link` doesn't already exist.
+#[cfg(windows)]
+fn symlink_if_not_exists(original: &Path, link: &Path) -> Result<(), String> {
+    if original.is_dir() {
+        std::os::windows::fs::symlink_dir(original, link)
+            .or_else(swallow_already_exists)
+            .map_err(|err| format!("Failed to create directory symlink: {err}"))
+    } else {
+        std::os::windows::fs::symlink_file(original, link)
+            .or_else(swallow_already_exists)
+            .map_err(|err| format!("Failed to create file symlink: {err}"))
+    }
+}
+
+/// Create a symlink from `link` to `original` if `link` doesn't already exist.
+#[cfg(not(windows))]
+fn symlink_if_not_exists(original: &Path, link: &Path) -> Result<(), String> {
+    std::os::unix::fs::symlink(original, link)
+        .or_else(swallow_already_exists)
+        .map_err(|err| format!("Failed to create symlink: {err}"))
+}
+
+fn swallow_already_exists(err: std::io::Error) -> std::io::Result<()> {
+    if err.kind() == std::io::ErrorKind::AlreadyExists {
+        Ok(())
+    } else {
+        Err(err)
+    }
+}
+
 /// A representation of expected command line arguments.
 struct Options {
     progname: String,
@@ -236,15 +293,14 @@
             env::var("TARGET").expect("missing TARGET")
         ))
         .output()
-        .map_err(|err| format!("Error running rustc to get target information: {}", err))?;
+        .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
+            "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))?;
+        .map_err(|err| format!("Non-UTF8 stdout from rustc: {err:?}"))?;
 
     Ok(parse_rustc_cfg_output(stdout))
 }
@@ -280,7 +336,7 @@
         Ok(_) => 0,
         Err(err) => {
             // Neatly print errors
-            eprintln!("{}", err);
+            eprintln!("{err}");
             1
         }
     });
diff --git a/cargo/cargo_build_script_runner/lib.rs b/cargo/cargo_build_script_runner/lib.rs
index b1aa793..8a1b765 100644
--- a/cargo/cargo_build_script_runner/lib.rs
+++ b/cargo/cargo_build_script_runner/lib.rs
@@ -103,12 +103,19 @@
     /// 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);
+        let mut buf = Vec::new();
+        while reader
+            .read_until(b'\n', &mut buf)
+            .expect("Cannot read line")
+            != 0
+        {
+            // like cargo, ignore any lines that are not valid utf8
+            if let Ok(line) = String::from_utf8(buf.clone()) {
+                if let Some(bso) = BuildScriptOutput::new(&line) {
+                    result.push(bso);
+                }
             }
-            line.clear();
+            buf.clear();
         }
         result
     }
@@ -176,11 +183,11 @@
 
         for flag in outputs {
             match flag {
-                BuildScriptOutput::Cfg(e) => compile_flags.push(format!("--cfg={}", e)),
+                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)),
+                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}")),
                 _ => {}
             }
         }
@@ -232,11 +239,11 @@
 cargo:rustc-env=SOME_PATH=/some/absolute/path/beep
 cargo:rustc-link-arg=-weak_framework
 cargo:rustc-link-arg=Metal
-",
+cargo:rustc-env=no_trailing_newline=true",
         );
         let reader = BufReader::new(buff);
         let result = BuildScriptOutput::outputs_from_reader(reader);
-        assert_eq!(result.len(), 12);
+        assert_eq!(result.len(), 13);
         assert_eq!(result[0], BuildScriptOutput::LinkLib("sdfsdf".to_owned()));
         assert_eq!(result[1], BuildScriptOutput::Env("FOO=BAR".to_owned()));
         assert_eq!(
@@ -266,14 +273,17 @@
             BuildScriptOutput::LinkArg("-weak_framework".to_owned())
         );
         assert_eq!(result[11], BuildScriptOutput::LinkArg("Metal".to_owned()));
-
+        assert_eq!(
+            result[12],
+            BuildScriptOutput::Env("no_trailing_newline=true".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()
+            "FOO=BAR\nBAR=FOO\nSOME_PATH=${pwd}/beep\nno_trailing_newline=true".to_owned()
         );
         assert_eq!(
             BuildScriptOutput::outputs_to_flags(&result, "/some/absolute/path"),
@@ -288,4 +298,22 @@
             }
         );
     }
+
+    #[test]
+    fn invalid_utf8() {
+        let buff = Cursor::new(
+            b"
+cargo:rustc-env=valid1=1
+cargo:rustc-env=invalid=\xc3\x28
+cargo:rustc-env=valid2=2
+",
+        );
+        let reader = BufReader::new(buff);
+        let result = BuildScriptOutput::outputs_from_reader(reader);
+        assert_eq!(result.len(), 2);
+        assert_eq!(
+            &BuildScriptOutput::outputs_to_env(&result, "/some/absolute/path"),
+            "valid1=1\nvalid2=2"
+        );
+    }
 }
diff --git a/cargo/defs.bzl b/cargo/defs.bzl
index 0ca086b..2801805 100644
--- a/cargo/defs.bzl
+++ b/cargo/defs.bzl
@@ -1,9 +1,21 @@
 """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")
+load(
+    "//cargo/private:cargo_build_script.bzl",
+    _cargo_dep_env = "cargo_dep_env",
+)
+load(
+    "//cargo/private:cargo_build_script_wrapper.bzl",
+    _cargo_build_script = "cargo_build_script",
+)
+load(
+    ":cargo_bootstrap.bzl",
+    _cargo_bootstrap_repository = "cargo_bootstrap_repository",
+    _cargo_env = "cargo_env",
+)
 
 cargo_bootstrap_repository = _cargo_bootstrap_repository
 cargo_env = _cargo_env
 
 cargo_build_script = _cargo_build_script
+cargo_dep_env = _cargo_dep_env
diff --git a/cargo/features.bzl b/cargo/features.bzl
new file mode 100644
index 0000000..29b9921
--- /dev/null
+++ b/cargo/features.bzl
@@ -0,0 +1,6 @@
+"""Public Cargo features for Bazel."""
+
+# `symlink-exec-root` feature will symlink the execroot to the build script execution directory.
+#
+# This is useful for building with hermetic C++ toolchains.
+SYMLINK_EXEC_ROOT_FEATURE = "symlink-exec-root"
diff --git a/cargo/private/cargo_build_script.bzl b/cargo/private/cargo_build_script.bzl
new file mode 100644
index 0000000..959ce25
--- /dev/null
+++ b/cargo/private/cargo_build_script.bzl
@@ -0,0 +1,454 @@
+"""Rules for Cargo build scripts (`build.rs` files)"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "CPP_COMPILE_ACTION_NAME", "C_COMPILE_ACTION_NAME")
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+load("//cargo:features.bzl", "SYMLINK_EXEC_ROOT_FEATURE")
+load("//rust:defs.bzl", "rust_common")
+
+# buildifier: disable=bzl-visibility
+load("//rust/private:providers.bzl", _DepInfo = "DepInfo")
+
+# 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 = "name_to_crate_name")
+load(":features.bzl", "feature_enabled")
+
+# Reexport for cargo_build_script_wrapper.bzl
+name_to_crate_name = _name_to_crate_name
+
+def get_cc_compile_args_and_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:
+        tuple: A tuple of the following items:
+            - (sequence): A flattened C command line flags for given action.
+            - (sequence): A flattened CXX command line flags for given action.
+            - (dict): C environment variables to be set for given action.
+    """
+    compile_variables = cc_common.create_compile_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+    )
+    cc_c_args = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = C_COMPILE_ACTION_NAME,
+        variables = compile_variables,
+    )
+    cc_cxx_args = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = CPP_COMPILE_ACTION_NAME,
+        variables = compile_variables,
+    )
+    cc_env = cc_common.get_environment_variables(
+        feature_configuration = feature_configuration,
+        action_name = C_COMPILE_ACTION_NAME,
+        variables = compile_variables,
+    )
+    return cc_c_args, cc_cxx_args, cc_env
+
+def _pwd_flags(args):
+    """Prefix execroot-relative paths of known arguments with ${pwd}.
+
+    Args:
+        args (list): List of tool arguments.
+
+    Returns:
+        list: The modified argument list.
+    """
+    res = []
+    for arg in args:
+        s, opt, path = arg.partition("--sysroot=")
+        if s == "" and not paths.is_absolute(path):
+            res.append("{}${{pwd}}/{}".format(opt, path))
+        else:
+            res.append(arg)
+    return res
+
+def _cargo_build_script_impl(ctx):
+    """The implementation for the `cargo_build_script` 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.str,
+        "NUM_JOBS": "1",
+        "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, "bin", cc_toolchain, feature_configuration, None)
+    env.update(**linker_env)
+    env["LD"] = linker
+    env["LDFLAGS"] = " ".join(_pwd_flags(link_args))
+
+    # MSVC requires INCLUDE to be set
+    cc_c_args, cc_cxx_args, cc_env = get_cc_compile_args_and_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
+
+        # Populate CFLAGS and CXXFLAGS that cc-rs relies on when building from source, in particular
+        # to determine the deployment target when building for apple platforms (`macosx-version-min`
+        # for example, itself derived from the `macos_minimum_os` Bazel argument).
+        env["CFLAGS"] = " ".join(_pwd_flags(cc_c_args))
+        env["CXXFLAGS"] = " ".join(_pwd_flags(cc_cxx_args))
+
+    # 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"
+
+    # Add environment variables from the Rust toolchain.
+    env.update(toolchain.env)
+
+    # Gather data from the `toolchains` attribute.
+    for target in ctx.attr.toolchains:
+        if DefaultInfo in target:
+            toolchain_tools.extend([
+                target[DefaultInfo].files,
+                target[DefaultInfo].default_runfiles.files,
+            ])
+        if platform_common.ToolchainInfo in target:
+            all_files = getattr(target[platform_common.ToolchainInfo], "all_files", depset([]))
+            if type(all_files) == "list":
+                all_files = depset(all_files)
+            toolchain_tools.append(all_files)
+
+    _merge_env_dict(env, 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)
+
+    if feature_enabled(ctx, SYMLINK_EXEC_ROOT_FEATURE):
+        env["RULES_RUST_SYMLINK_EXEC_ROOT"] = "1"
+
+    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]),
+            out_dir = depset([out_dir]),
+        ),
+    ]
+
+cargo_build_script = 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 = _cargo_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],
+            cfg = "exec",
+        ),
+        "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_type")),
+        "@bazel_tools//tools/cpp:toolchain_type",
+    ],
+    incompatible_use_toolchain_transition = True,
+)
+
+def _merge_env_dict(prefix_dict, suffix_dict):
+    """Merges suffix_dict into prefix_dict, appending rather than replacing certain env vars."""
+    for key in ["CFLAGS", "CXXFLAGS", "LDFLAGS"]:
+        if key in prefix_dict and key in suffix_dict and prefix_dict[key]:
+            prefix_dict[key] += " " + suffix_dict.pop(key)
+    prefix_dict.update(suffix_dict)
+
+def name_to_pkg_name(name):
+    """Sanitize the name of cargo_build_script targets.
+
+    Args:
+        name (str): The name value pass to the `cargo_build_script` wrapper.
+
+    Returns:
+        str: A cleaned up name for a build script target.
+    """
+    if name.endswith("_build_script"):
+        return name[:-len("_build_script")]
+    return name
+
+def _cargo_dep_env_implementation(ctx):
+    empty_file = ctx.actions.declare_file(ctx.label.name + ".empty_file")
+    empty_dir = ctx.actions.declare_directory(ctx.label.name + ".empty_dir")
+    ctx.actions.write(
+        output = empty_file,
+        content = "",
+    )
+    ctx.actions.run(
+        outputs = [empty_dir],
+        executable = "true",
+    )
+
+    build_infos = []
+    out_dir = ctx.file.out_dir
+    if out_dir:
+        if not out_dir.is_directory:
+            fail("out_dir must be a directory artifact")
+
+        # BuildInfos in this list are collected up for all transitive cargo_build_script
+        # dependencies. This is important for any flags set in `dep_env` which reference this
+        # `out_dir`.
+        #
+        # TLDR: This BuildInfo propagates up build script dependencies.
+        build_infos.append(BuildInfo(
+            dep_env = empty_file,
+            flags = empty_file,
+            link_flags = empty_file,
+            link_search_paths = empty_file,
+            out_dir = out_dir,
+            rustc_env = empty_file,
+        ))
+    return [
+        DefaultInfo(files = depset(ctx.files.src)),
+        # Parts of this BuildInfo is used when building all transitive dependencies
+        # (cargo_build_script and otherwise), alongside the DepInfo. This is how other rules
+        # identify this one as a valid dependency, but we don't otherwise have a use for it.
+        #
+        # TLDR: This BuildInfo propagates up normal (non build script) depenencies.
+        #
+        # In the future, we could consider setting rustc_env here, and also propagating dep_dir
+        # so files in it can be referenced there.
+        BuildInfo(
+            dep_env = empty_file,
+            flags = empty_file,
+            link_flags = empty_file,
+            link_search_paths = empty_file,
+            out_dir = empty_dir,
+            rustc_env = empty_file,
+        ),
+        # Information here is used directly by dependencies, and it is an error to have more than
+        # one dependency which sets this. This is the main way to specify information from build
+        # scripts, which is what we're looking to do.
+        _DepInfo(
+            dep_env = ctx.file.src,
+            direct_crates = depset(),
+            link_search_path_files = depset(),
+            transitive_build_infos = depset(direct = build_infos),
+            transitive_crate_outputs = depset(),
+            transitive_crates = depset(),
+            transitive_noncrates = depset(),
+        ),
+    ]
+
+cargo_dep_env = rule(
+    implementation = _cargo_dep_env_implementation,
+    doc = (
+        "A rule for generating variables for dependent `cargo_build_script`s " +
+        "without a build script. This is useful for using Bazel rules instead " +
+        "of a build script, while also generating configuration information " +
+        "for build scripts which depend on this crate."
+    ),
+    attrs = {
+        "out_dir": attr.label(
+            doc = dedent("""\
+                Folder containing additional inputs when building all direct dependencies.
+
+                This has the same effect as a `cargo_build_script` which prints
+                puts files into `$OUT_DIR`, but without requiring a build script.
+            """),
+            allow_single_file = True,
+            mandatory = False,
+        ),
+        "src": attr.label(
+            doc = dedent("""\
+                File containing additional environment variables to set for build scripts of direct dependencies.
+
+                This has the same effect as a `cargo_build_script` which prints
+                `cargo:VAR=VALUE` lines, but without requiring a build script.
+
+                This 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 (`\\\\`).
+            """),
+            allow_single_file = True,
+            mandatory = True,
+        ),
+    },
+)
diff --git a/cargo/private/cargo_build_script_wrapper.bzl b/cargo/private/cargo_build_script_wrapper.bzl
new file mode 100644
index 0000000..e354acb
--- /dev/null
+++ b/cargo/private/cargo_build_script_wrapper.bzl
@@ -0,0 +1,153 @@
+"""Rules for Cargo build scripts (`build.rs` files)"""
+
+load(
+    "//cargo/private:cargo_build_script.bzl",
+    "name_to_crate_name",
+    "name_to_pkg_name",
+    _build_script_run = "cargo_build_script",
+)
+load("//rust:defs.bzl", "rust_binary")
+
+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:defs.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. An exception is the `compatible_with`
+            attribute, which shouldn't be forwarded to the `rust_binary`, as the `rust_binary` is only
+            built and used in `exec` mode. We propagate the `compatible_with` attribute to the `_build_scirpt_run`
+            target.
+    """
+
+    # This duplicates the code in _cargo_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 (_cargo_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")
+    build_script_kwargs = {}
+    binary_kwargs = kwargs
+    if "compatible_with" in kwargs:
+        build_script_kwargs["compatible_with"] = kwargs["compatible_with"]
+        binary_kwargs.pop("compatible_with")
+
+    if "toolchains" in kwargs:
+        build_script_kwargs["toolchains"] = kwargs["toolchains"]
+
+    if "features" in kwargs:
+        build_script_kwargs["features"] = kwargs["features"]
+
+    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,
+        **binary_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,
+        **build_script_kwargs
+    )
diff --git a/cargo/private/cargo_utils.bzl b/cargo/private/cargo_utils.bzl
index ef8eb28..b70d8e2 100644
--- a/cargo/private/cargo_utils.bzl
+++ b/cargo/private/cargo_utils.bzl
@@ -6,6 +6,7 @@
         template,
         abi = None,
         arch = None,
+        channel = None,
         system = None,
         tool = None,
         triple = None,
@@ -17,6 +18,7 @@
         template (str): The template to use for rendering
         abi (str, optional): The host ABI
         arch (str, optional): The host CPU architecture
+        channel (str, optional): The toolchain channel. Eg. `stable`, `nightly`.
         system (str, optional): The host system name
         tool (str, optional): The tool to expect in the particular repository.
             Eg. `cargo`, `rustc`, `stdlib`.
@@ -47,16 +49,20 @@
     if version:
         template = template.replace("{version}", version)
 
+    if channel:
+        template = template.replace("{channel}", channel)
+
     return template
 
-def get_rust_tools(cargo_template, rustc_template, host_triple, version):
+def get_rust_tools(cargo_template, rustc_template, host_triple, channel, 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.
+        channel (str): The Rust toolchain channel.
+        version (str): The version (or iso date in case of beta or nightly channels) of Cargo+Rustc to use.
 
     Returns:
         struct: A struct containing the labels of expected tools
@@ -66,6 +72,7 @@
     cargo_label = Label(_resolve_repository_template(
         template = cargo_template,
         version = version,
+        channel = channel,
         triple = host_triple.str,
         arch = host_triple.arch,
         vendor = host_triple.vendor,
@@ -77,6 +84,7 @@
     rustc_label = Label(_resolve_repository_template(
         template = rustc_template,
         version = version,
+        channel = channel,
         triple = host_triple.str,
         arch = host_triple.arch,
         vendor = host_triple.vendor,
diff --git a/cargo/private/features.bzl b/cargo/private/features.bzl
new file mode 100644
index 0000000..5d396b9
--- /dev/null
+++ b/cargo/private/features.bzl
@@ -0,0 +1,24 @@
+"""Feature helpers."""
+
+def feature_enabled(ctx, feature_name, default = False):
+    """Check if a feature is enabled.
+
+    If the feature is explicitly enabled or disabled, return accordingly.
+
+    In the case where the feature is not explicitly enabled or disabled, return the default value.
+
+    Args:
+        ctx: The context object.
+        feature_name: The name of the feature.
+        default: The default value to return if the feature is not explicitly enabled or disabled.
+
+    Returns:
+        Boolean defining whether the feature is enabled.
+    """
+    if feature_name in ctx.disabled_features:
+        return False
+
+    if feature_name in ctx.features:
+        return True
+
+    return default