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/rust/private/BUILD.bazel b/rust/private/BUILD.bazel
index d175b28..3c963cd 100644
--- a/rust/private/BUILD.bazel
+++ b/rust/private/BUILD.bazel
@@ -1,6 +1,5 @@
 load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
 load("//rust/private:rust_analyzer.bzl", "rust_analyzer_detect_sysroot")
-load("//rust/private:rustfmt.bzl", "rustfmt_workspace_name")
 load("//rust/private:stamp.bzl", "stamp_build_setting")
 
 bzl_library(
@@ -16,8 +15,3 @@
     name = "rust_analyzer_detect_sysroot",
     visibility = ["//visibility:public"],
 )
-
-rustfmt_workspace_name(
-    name = "rustfmt_workspace_name",
-    visibility = ["//visibility:public"],
-)
diff --git a/rust/private/clippy.bzl b/rust/private/clippy.bzl
index 21f3965..d01c13d 100644
--- a/rust/private/clippy.bzl
+++ b/rust/private/clippy.bzl
@@ -45,7 +45,7 @@
     build_setting = config.string_list(flag = True),
 )
 
-def _get_clippy_ready_crate_info(target, aspect_ctx):
+def _get_clippy_ready_crate_info(target, aspect_ctx = None):
     """Check that a target is suitable for clippy and extract the `CrateInfo` provider from it.
 
     Args:
@@ -72,11 +72,13 @@
                 return None
 
     # Obviously ignore any targets that don't contain `CrateInfo`
-    if rust_common.crate_info not in target:
+    if rust_common.crate_info in target:
+        return target[rust_common.crate_info]
+    elif rust_common.test_crate_info in target:
+        return target[rust_common.test_crate_info].crate
+    else:
         return None
 
-    return target[rust_common.crate_info]
-
 def _clippy_aspect_impl(target, ctx):
     crate_info = _get_clippy_ready_crate_info(target, ctx)
     if not crate_info:
@@ -215,8 +217,12 @@
             doc = "The desired `--error-format` flags for clippy",
             default = "//:error_format",
         ),
-        "_extra_rustc_flag": attr.label(default = "//:extra_rustc_flag"),
-        "_extra_rustc_flags": attr.label(default = "//:extra_rustc_flags"),
+        "_extra_rustc_flag": attr.label(
+            default = Label("//:extra_rustc_flag"),
+        ),
+        "_per_crate_rustc_flag": attr.label(
+            default = Label("//:experimental_per_crate_rustc_flag"),
+        ),
         "_process_wrapper": attr.label(
             doc = "A process wrapper for running clippy on all platforms",
             default = Label("//util/process_wrapper"),
@@ -225,6 +231,10 @@
         ),
     },
     provides = [ClippyInfo],
+    required_providers = [
+        [rust_common.crate_info],
+        [rust_common.test_crate_info],
+    ],
     toolchains = [
         str(Label("//rust:toolchain_type")),
         "@bazel_tools//tools/cpp:toolchain_type",
@@ -272,7 +282,10 @@
     attrs = {
         "deps": attr.label_list(
             doc = "Rust targets to run clippy on.",
-            providers = [rust_common.crate_info],
+            providers = [
+                [rust_common.crate_info],
+                [rust_common.test_crate_info],
+            ],
             aspects = [rust_clippy_aspect],
         ),
     },
diff --git a/rust/private/common.bzl b/rust/private/common.bzl
index 025c4d7..f6063a0 100644
--- a/rust/private/common.bzl
+++ b/rust/private/common.bzl
@@ -23,7 +23,7 @@
 In the Bazel lingo, `rust_common` gives the access to the Rust Sandwich API.
 """
 
-load(":providers.bzl", "CrateInfo", "DepInfo", "StdLibInfo", "TestCrateInfo")
+load(":providers.bzl", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo", "StdLibInfo", "TestCrateInfo")
 
 # This constant only represents the default value for attributes and macros
 # defined in `rules_rust`. Like any attribute public attribute, it can be
@@ -31,7 +31,9 @@
 #
 # Note: Code in `.github/workflows/crate_universe.yaml` looks for this line, if
 # you remove it or change its format, you will also need to update that code.
-DEFAULT_RUST_VERSION = "1.62.1"
+DEFAULT_RUST_VERSION = "1.70.0"
+
+DEFAULT_NIGHTLY_ISO_DATE = "2023-06-01"
 
 def _create_crate_info(**kwargs):
     """A constructor for a `CrateInfo` provider
@@ -57,7 +59,9 @@
     create_crate_info = _create_crate_info,
     crate_info = CrateInfo,
     dep_info = DepInfo,
+    dep_variant_info = DepVariantInfo,
     stdlib_info = StdLibInfo,
     test_crate_info = TestCrateInfo,
+    crate_group_info = CrateGroupInfo,
     default_version = DEFAULT_RUST_VERSION,
 )
diff --git a/rust/private/dummy_cc_toolchain/BUILD.bazel b/rust/private/dummy_cc_toolchain/BUILD.bazel
index 004d233..848480f 100644
--- a/rust/private/dummy_cc_toolchain/BUILD.bazel
+++ b/rust/private/dummy_cc_toolchain/BUILD.bazel
@@ -1,4 +1,5 @@
-load(":dummy_cc_toolchain.bzl", "dummy_cc_toolchain")
+load("@rules_cc//cc:defs.bzl", "cc_toolchain")
+load(":dummy_cc_toolchain.bzl", "dummy_cc_config", "dummy_cc_toolchain")
 
 dummy_cc_toolchain(name = "dummy_cc_wasm32")
 
@@ -8,6 +9,29 @@
 toolchain(
     name = "dummy_cc_wasm32_toolchain",
     target_compatible_with = ["//rust/platform/cpu:wasm32"],
-    toolchain = ":dummy_cc_wasm32",
+    toolchain = ":dummy_cc_wasm32_toolchain_cc",
     toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
 )
+
+cc_toolchain(
+    name = "dummy_cc_wasm32_toolchain_cc",
+    all_files = ":empty",
+    compiler_files = ":empty",
+    dwp_files = ":empty",
+    linker_files = ":empty",
+    objcopy_files = ":empty",
+    strip_files = ":empty",
+    supports_param_files = 0,
+    toolchain_config = ":cc_toolchain_config",
+    toolchain_identifier = "dummy_wasm32_cc",
+)
+
+dummy_cc_config(
+    name = "cc_toolchain_config",
+)
+
+filegroup(
+    name = "empty",
+    srcs = [],
+    visibility = ["//:__subpackages__"],
+)
diff --git a/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl b/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl
index 8ca8c8f..8cbf617 100644
--- a/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl
+++ b/rust/private/dummy_cc_toolchain/dummy_cc_toolchain.bzl
@@ -1,4 +1,5 @@
-# buildifier: disable=module-docstring
+"""Cc toolchain definitions for use on wasm platforms"""
+
 def _dummy_cc_toolchain_impl(_ctx):
     # The `all_files` attribute is referenced by rustc_compile_action().
     return [platform_common.ToolchainInfo(all_files = depset([]))]
@@ -7,3 +8,23 @@
     implementation = _dummy_cc_toolchain_impl,
     attrs = {},
 )
+
+# dummy values from https://bazel.build/tutorials/ccp-toolchain-config#configuring_the_c_toolchain
+def _config_impl(ctx):
+    return cc_common.create_cc_toolchain_config_info(
+        ctx = ctx,
+        toolchain_identifier = "dummy-wasm32-cc-toolchain",
+        host_system_name = "unknown",
+        target_system_name = "unknown",
+        target_cpu = "unknown",
+        target_libc = "unknown",
+        compiler = "unknown",
+        abi_version = "unknown",
+        abi_libc_version = "unknown",
+    )
+
+dummy_cc_config = rule(
+    implementation = _config_impl,
+    attrs = {},
+    provides = [CcToolchainConfigInfo],
+)
diff --git a/rust/private/extensions.bzl b/rust/private/extensions.bzl
new file mode 100644
index 0000000..05f9076
--- /dev/null
+++ b/rust/private/extensions.bzl
@@ -0,0 +1,12 @@
+"""Bzlmod module extensions that are only used internally"""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("//rust/private:repository_utils.bzl", "TINYJSON_KWARGS")
+
+def _internal_deps_impl(_module_ctx):
+    http_archive(**TINYJSON_KWARGS)
+
+internal_deps = module_extension(
+    doc = "Dependencies for rules_rust",
+    implementation = _internal_deps_impl,
+)
diff --git a/rust/private/providers.bzl b/rust/private/providers.bzl
index 464444b..b26cc67 100644
--- a/rust/private/providers.bzl
+++ b/rust/private/providers.bzl
@@ -19,6 +19,7 @@
     fields = {
         "aliases": "Dict[Label, String]: Renamed and aliased crates",
         "compile_data": "depset[File]: Compile data required by this crate.",
+        "compile_data_targets": "depset[Label]: Compile data targets required by this crate.",
         "deps": "depset[DepVariantInfo]: This crate's (rust or cc) dependencies' providers.",
         "edition": "str: The edition of this crate.",
         "is_test": "bool: If the crate is being compiled in a test context",
@@ -56,6 +57,13 @@
     },
 )
 
+CrateGroupInfo = provider(
+    doc = "A provider containing a group of crates.",
+    fields = {
+        "dep_variant_infos": "depset[DepVariantInfo]: Dependency information from all crates in the group.",
+    },
+)
+
 BuildInfo = provider(
     doc = "A provider containing `rustc` build settings for a given Crate.",
     fields = {
@@ -71,11 +79,13 @@
 DepVariantInfo = provider(
     doc = "A wrapper provider for a dependency of a crate. The dependency can be a Rust " +
           "dependency, in which case the `crate_info` and `dep_info` fields will be populated, " +
-          "a Rust build script dependency, in which case `build_info` will be populated, or a " +
-          "C/C++ dependency, in which case `cc_info` will be populated.",
+          "a Rust build script dependency, in which case `build_info` will be populated, a C/C++" +
+          "dependency, in which case `cc_info` will be populated, or a Rust crate group, in which" +
+          "case `crate_group_info` will be populated.",
     fields = {
         "build_info": "BuildInfo: The BuildInfo of a Rust dependency",
         "cc_info": "CcInfo: The CcInfo of a C/C++ dependency",
+        "crate_group_info": "CrateGroupInfo: The CrateGroupInfo of a Rust crate group dependency",
         "crate_info": "CrateInfo: The CrateInfo of a Rust dependency",
         "dep_info": "DepInfo: The DepInfo of a Rust dependency",
     },
@@ -93,6 +103,7 @@
         "core_files": "List[File]: `.a` files related to the `core` and `adler` modules",
         "dot_a_files": "Depset[File]: Generated `.a` files",
         "memchr_files": "Depset[File]: `.a` files associated with the `memchr` module.",
+        "panic_files": "Depset[File]: `.a` files associated with `panic_unwind` and `panic_abort`.",
         "self_contained_files": "List[File]: All `.o` files from the `self-contained` directory.",
         "srcs": "List[Target]: All targets from the original `srcs` attribute.",
         "std_files": "Depset[File]: `.a` files associated with the `std` module.",
diff --git a/rust/private/repository_utils.bzl b/rust/private/repository_utils.bzl
index 86d3f78..1f5a01a 100644
--- a/rust/private/repository_utils.bzl
+++ b/rust/private/repository_utils.bzl
@@ -7,11 +7,22 @@
     "system_to_dylib_ext",
     "system_to_staticlib_ext",
     "system_to_stdlib_linkflags",
-    "triple_to_system",
 )
+load("//rust/private:common.bzl", "DEFAULT_NIGHTLY_ISO_DATE")
 
 DEFAULT_TOOLCHAIN_NAME_PREFIX = "toolchain_for"
 DEFAULT_STATIC_RUST_URL_TEMPLATES = ["https://static.rust-lang.org/dist/{}.tar.gz"]
+DEFAULT_NIGHTLY_VERSION = "nightly/{}".format(DEFAULT_NIGHTLY_ISO_DATE)
+DEFAULT_EXTRA_TARGET_TRIPLES = ["wasm32-unknown-unknown", "wasm32-wasi"]
+
+TINYJSON_KWARGS = dict(
+    name = "rules_rust_tinyjson",
+    sha256 = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a",
+    url = "https://crates.io/api/v1/crates/tinyjson/2.5.1/download",
+    strip_prefix = "tinyjson-2.5.1",
+    type = "tar.gz",
+    build_file = "@rules_rust//util/process_wrapper:BUILD.tinyjson.bazel",
+)
 
 _build_file_for_compiler_template = """\
 filegroup(
@@ -51,12 +62,11 @@
     Returns:
         str: The contents of a BUILD file
     """
-    system = triple_to_system(target_triple)
     return _build_file_for_compiler_template.format(
-        binary_ext = system_to_binary_ext(system),
-        staticlib_ext = system_to_staticlib_ext(system),
-        dylib_ext = system_to_dylib_ext(system),
-        target_triple = target_triple,
+        binary_ext = system_to_binary_ext(target_triple.system),
+        staticlib_ext = system_to_staticlib_ext(target_triple.system),
+        dylib_ext = system_to_dylib_ext(target_triple.system),
+        target_triple = target_triple.str,
     )
 
 _build_file_for_cargo_template = """\
@@ -75,9 +85,8 @@
     Returns:
         str: The contents of a BUILD file
     """
-    system = triple_to_system(target_triple)
     return _build_file_for_cargo_template.format(
-        binary_ext = system_to_binary_ext(system),
+        binary_ext = system_to_binary_ext(target_triple.system),
     )
 
 _build_file_for_rustfmt_template = """\
@@ -103,9 +112,8 @@
     Returns:
         str: The contents of a BUILD file
     """
-    system = triple_to_system(target_triple)
     return _build_file_for_rustfmt_template.format(
-        binary_ext = system_to_binary_ext(system),
+        binary_ext = system_to_binary_ext(target_triple.system),
     )
 
 _build_file_for_clippy_template = """\
@@ -116,6 +124,26 @@
 )
 """
 
+_build_file_for_rust_analyzer_proc_macro_srv = """\
+filegroup(
+   name = "rust_analyzer_proc_macro_srv",
+   srcs = ["libexec/rust-analyzer-proc-macro-srv{binary_ext}"],
+   visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_rust_analyzer_proc_macro_srv(exec_triple):
+    """Emits a BUILD file the rust_analyzer_proc_macro_srv archive.
+
+    Args:
+        exec_triple (str): The triple of the exec platform
+    Returns:
+        str: The contents of a BUILD file
+    """
+    return _build_file_for_rust_analyzer_proc_macro_srv.format(
+        binary_ext = system_to_binary_ext(exec_triple.system),
+    )
+
 def BUILD_for_clippy(target_triple):
     """Emits a BUILD file the clippy archive.
 
@@ -125,8 +153,9 @@
     Returns:
         str: The contents of a BUILD file
     """
-    system = triple_to_system(target_triple)
-    return _build_file_for_clippy_template.format(binary_ext = system_to_binary_ext(system))
+    return _build_file_for_clippy_template.format(
+        binary_ext = system_to_binary_ext(target_triple.system),
+    )
 
 _build_file_for_llvm_tools = """\
 filegroup(
@@ -146,15 +175,14 @@
     """Emits a BUILD file the llvm-tools binaries.
 
     Args:
-        target_triple (str): The triple of the target platform
+        target_triple (struct): The triple of the target platform
 
     Returns:
         str: The contents of a BUILD file
     """
-    system = triple_to_system(target_triple)
     return _build_file_for_llvm_tools.format(
-        binary_ext = system_to_binary_ext(system),
-        target_triple = target_triple,
+        binary_ext = system_to_binary_ext(target_triple.system),
+        target_triple = target_triple.str,
     )
 
 _build_file_for_stdlib_template = """\
@@ -187,17 +215,16 @@
     """Emits a BUILD file the stdlib archive.
 
     Args:
-        target_triple (str): The triple of the target platform
+        target_triple (triple): The triple of the target platform
 
     Returns:
         str: The contents of a BUILD file
     """
-    system = triple_to_system(target_triple)
     return _build_file_for_stdlib_template.format(
-        binary_ext = system_to_binary_ext(system),
-        staticlib_ext = system_to_staticlib_ext(system),
-        dylib_ext = system_to_dylib_ext(system),
-        target_triple = target_triple,
+        binary_ext = system_to_binary_ext(target_triple.system),
+        staticlib_ext = system_to_staticlib_ext(target_triple.system),
+        dylib_ext = system_to_dylib_ext(target_triple.system),
+        target_triple = target_triple.str,
     )
 
 _build_file_for_rust_toolchain_template = """\
@@ -205,95 +232,98 @@
 
 rust_toolchain(
     name = "{toolchain_name}",
-    rust_doc = "@{workspace_name}//:rustdoc",
-    rust_std = "@{workspace_name}//:rust_std-{target_triple}",
-    rustc = "@{workspace_name}//:rustc",
+    rust_doc = "//:rustdoc",
+    rust_std = "//:rust_std-{target_triple}",
+    rustc = "//:rustc",
     rustfmt = {rustfmt_label},
-    cargo = "@{workspace_name}//:cargo",
-    clippy_driver = "@{workspace_name}//:clippy_driver_bin",
+    cargo = "//:cargo",
+    clippy_driver = "//:clippy_driver_bin",
     llvm_cov = {llvm_cov_label},
     llvm_profdata = {llvm_profdata_label},
-    rustc_lib = "@{workspace_name}//:rustc_lib",
-    rustc_srcs = {rustc_srcs},
+    rustc_lib = "//:rustc_lib",
     allocator_library = {allocator_library},
+    global_allocator_library = {global_allocator_library},
     binary_ext = "{binary_ext}",
     staticlib_ext = "{staticlib_ext}",
     dylib_ext = "{dylib_ext}",
     stdlib_linkflags = [{stdlib_linkflags}],
-    os = "{system}",
     default_edition = "{default_edition}",
     exec_triple = "{exec_triple}",
     target_triple = "{target_triple}",
     visibility = ["//visibility:public"],
+    extra_rustc_flags = {extra_rustc_flags},
+    extra_exec_rustc_flags = {extra_exec_rustc_flags},
 )
 """
 
 def BUILD_for_rust_toolchain(
-        workspace_name,
         name,
         exec_triple,
         target_triple,
-        include_rustc_srcs,
         allocator_library,
+        global_allocator_library,
         default_edition,
         include_rustfmt,
         include_llvm_tools,
-        stdlib_linkflags = None):
+        stdlib_linkflags = None,
+        extra_rustc_flags = None,
+        extra_exec_rustc_flags = None):
     """Emits a toolchain declaration to match an existing compiler and stdlib.
 
     Args:
-        workspace_name (str): The name of the workspace that this toolchain resides in
         name (str): The name of the toolchain declaration
-        exec_triple (str): The rust-style target that this compiler runs on
-        target_triple (str): The rust-style target triple of the tool
-        include_rustc_srcs (bool, optional): Whether to download rustc's src code. This is required in order to use rust-analyzer support. Defaults to False.
+        exec_triple (triple): The rust-style target that this compiler runs on
+        target_triple (triple): The rust-style target triple of the tool
         allocator_library (str, optional): Target that provides allocator functions when rust_library targets are embedded in a cc_binary.
+        global_allocator_library (str, optional): Target that provides allocator functions when a global allocator is used with cc_common_link.
+                                                  This target is only used in the target configuration; exec builds still use the symbols provided
+                                                  by the `allocator_library` target.
         default_edition (str): Default Rust edition.
         include_rustfmt (bool): Whether rustfmt is present in the toolchain.
         include_llvm_tools (bool): Whether llvm-tools are present in the toolchain.
         stdlib_linkflags (list, optional): Overriden flags needed for linking to rust
                                            stdlib, akin to BAZEL_LINKLIBS. Defaults to
                                            None.
-
+        extra_rustc_flags (list, optional): Extra flags to pass to rustc in non-exec configuration.
+        extra_exec_rustc_flags (list, optional): Extra flags to pass to rustc in exec configuration.
 
     Returns:
         str: A rendered template of a `rust_toolchain` declaration
     """
-    system = triple_to_system(target_triple)
     if stdlib_linkflags == None:
-        stdlib_linkflags = ", ".join(['"%s"' % x for x in system_to_stdlib_linkflags(system)])
+        stdlib_linkflags = ", ".join(['"%s"' % x for x in system_to_stdlib_linkflags(target_triple.system)])
 
-    rustc_srcs = "None"
-    if include_rustc_srcs:
-        rustc_srcs = "\"@{workspace_name}//lib/rustlib/src:rustc_srcs\"".format(workspace_name = workspace_name)
     rustfmt_label = "None"
     if include_rustfmt:
-        rustfmt_label = "\"@{workspace_name}//:rustfmt_bin\"".format(workspace_name = workspace_name)
+        rustfmt_label = "\"//:rustfmt_bin\""
     llvm_cov_label = "None"
     llvm_profdata_label = "None"
     if include_llvm_tools:
-        llvm_cov_label = "\"@{workspace_name}//:llvm_cov_bin\"".format(workspace_name = workspace_name)
-        llvm_profdata_label = "\"@{workspace_name}//:llvm_profdata_bin\"".format(workspace_name = workspace_name)
+        llvm_cov_label = "\"//:llvm_cov_bin\""
+        llvm_profdata_label = "\"//:llvm_profdata_bin\""
     allocator_library_label = "None"
     if allocator_library:
         allocator_library_label = "\"{allocator_library}\"".format(allocator_library = allocator_library)
+    global_allocator_library_label = "None"
+    if global_allocator_library:
+        global_allocator_library_label = "\"{global_allocator_library}\"".format(global_allocator_library = global_allocator_library)
 
     return _build_file_for_rust_toolchain_template.format(
         toolchain_name = name,
-        workspace_name = workspace_name,
-        binary_ext = system_to_binary_ext(system),
-        staticlib_ext = system_to_staticlib_ext(system),
-        dylib_ext = system_to_dylib_ext(system),
-        rustc_srcs = rustc_srcs,
+        binary_ext = system_to_binary_ext(target_triple.system),
+        staticlib_ext = system_to_staticlib_ext(target_triple.system),
+        dylib_ext = system_to_dylib_ext(target_triple.system),
         allocator_library = allocator_library_label,
+        global_allocator_library = global_allocator_library_label,
         stdlib_linkflags = stdlib_linkflags,
-        system = system,
         default_edition = default_edition,
-        exec_triple = exec_triple,
-        target_triple = target_triple,
+        exec_triple = exec_triple.str,
+        target_triple = target_triple.str,
         rustfmt_label = rustfmt_label,
         llvm_cov_label = llvm_cov_label,
         llvm_profdata_label = llvm_profdata_label,
+        extra_rustc_flags = extra_rustc_flags,
+        extra_exec_rustc_flags = extra_exec_rustc_flags,
     )
 
 _build_file_for_toolchain_template = """\
@@ -303,6 +333,7 @@
     target_compatible_with = {target_constraint_sets_serialized},
     toolchain = "{toolchain}",
     toolchain_type = "{toolchain_type}",
+    {target_settings}
 )
 """
 
@@ -310,133 +341,149 @@
         name,
         toolchain,
         toolchain_type,
+        target_settings,
         target_compatible_with,
         exec_compatible_with):
+    target_settings_value = "target_settings = {},".format(json.encode(target_settings)) if target_settings else "# target_settings = []"
+
     return _build_file_for_toolchain_template.format(
         name = name,
-        exec_constraint_sets_serialized = exec_compatible_with,
-        target_constraint_sets_serialized = target_compatible_with,
+        exec_constraint_sets_serialized = json.encode(exec_compatible_with),
+        target_constraint_sets_serialized = json.encode(target_compatible_with),
         toolchain = toolchain,
         toolchain_type = toolchain_type,
+        target_settings = target_settings_value,
     )
 
-def load_rustfmt(ctx):
+def load_rustfmt(ctx, target_triple, version, iso_date):
     """Loads a rustfmt binary and yields corresponding BUILD for it
 
     Args:
-        ctx (repository_ctx): The repository rule's context object
+        ctx (repository_ctx): The repository rule's context object.
+        target_triple (struct): The platform triple to download rustfmt for.
+        version (str): The version or channel of rustfmt.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
 
     Returns:
         str: The BUILD file contents for this rustfmt binary
     """
-    target_triple = ctx.attr.exec_triple
 
     load_arbitrary_tool(
         ctx,
-        iso_date = ctx.attr.iso_date,
+        iso_date = iso_date,
         target_triple = target_triple,
         tool_name = "rustfmt",
         tool_subdirectories = ["rustfmt-preview"],
-        version = ctx.attr.rustfmt_version,
+        version = version,
     )
 
     return BUILD_for_rustfmt(target_triple)
 
-def load_rust_compiler(ctx):
+def load_rust_compiler(ctx, iso_date, target_triple, version):
     """Loads a rust compiler and yields corresponding BUILD for it
 
     Args:
         ctx (repository_ctx): A repository_ctx.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
+        target_triple (struct): The Rust-style target that this compiler runs on.
+        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
 
     Returns:
         str: The BUILD file contents for this compiler and compiler library
     """
 
-    target_triple = ctx.attr.exec_triple
     load_arbitrary_tool(
         ctx,
-        iso_date = ctx.attr.iso_date,
+        iso_date = iso_date,
         target_triple = target_triple,
         tool_name = "rustc",
         tool_subdirectories = ["rustc"],
-        version = ctx.attr.version,
+        version = version,
     )
 
     return BUILD_for_compiler(target_triple)
 
-def load_clippy(ctx):
+def load_clippy(ctx, iso_date, target_triple, version):
     """Loads Clippy and yields corresponding BUILD for it
 
     Args:
         ctx (repository_ctx): A repository_ctx.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
+        target_triple (struct): The Rust-style target that this compiler runs on.
+        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
 
     Returns:
         str: The BUILD file contents for Clippy
     """
-
-    target_triple = ctx.attr.exec_triple
     load_arbitrary_tool(
         ctx,
-        iso_date = ctx.attr.iso_date,
+        iso_date = iso_date,
         target_triple = target_triple,
         tool_name = "clippy",
         tool_subdirectories = ["clippy-preview"],
-        version = ctx.attr.version,
+        version = version,
     )
 
     return BUILD_for_clippy(target_triple)
 
-def load_cargo(ctx):
+def load_cargo(ctx, iso_date, target_triple, version):
     """Loads Cargo and yields corresponding BUILD for it
 
     Args:
         ctx (repository_ctx): A repository_ctx.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
+        target_triple (struct): The Rust-style target that this compiler runs on.
+        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
 
     Returns:
         str: The BUILD file contents for Cargo
     """
 
-    target_triple = ctx.attr.exec_triple
     load_arbitrary_tool(
         ctx,
-        iso_date = ctx.attr.iso_date,
+        iso_date = iso_date,
         target_triple = target_triple,
         tool_name = "cargo",
         tool_subdirectories = ["cargo"],
-        version = ctx.attr.version,
+        version = version,
     )
 
     return BUILD_for_cargo(target_triple)
 
-def should_include_rustc_srcs(repository_ctx):
-    """Determing whether or not to include rustc sources in the toolchain.
+def includes_rust_analyzer_proc_macro_srv(version, iso_date):
+    """Determine whether or not the rust_analyzer_proc_macro_srv binary in available in the given version of Rust.
 
     Args:
-        repository_ctx (repository_ctx): The repository rule's context object
+        version (str): The version of the tool among \"nightly\", \"beta\", or an exact version.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
 
     Returns:
-        bool: Whether or not to include rustc source files in a `rustc_toolchain`
+        bool: Whether or not the binary is expected to be included
     """
 
-    # The environment variable will always take precedence over the attribute.
-    include_rustc_srcs_env = repository_ctx.os.environ.get("RULES_RUST_TOOLCHAIN_INCLUDE_RUSTC_SRCS")
-    if include_rustc_srcs_env != None:
-        return include_rustc_srcs_env.lower() in ["true", "1"]
+    if version == "nightly":
+        return iso_date >= "2022-09-21"
+    elif version == "beta":
+        return False
+    elif version >= "1.64.0":
+        return True
 
-    return getattr(repository_ctx.attr, "include_rustc_srcs", False)
+    return False
 
-def load_rust_src(ctx, sha256 = ""):
+def load_rust_src(ctx, iso_date, version, sha256 = ""):
     """Loads the rust source code. Used by the rust-analyzer rust-project.json generator.
 
     Args:
         ctx (ctx): A repository_ctx.
+        version (str): The version of the tool among "nightly", "beta', or an exact version.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
         sha256 (str): The sha256 value for the `rust-src` artifact
     """
-    tool_suburl = produce_tool_suburl("rust-src", None, ctx.attr.version, ctx.attr.iso_date)
+    tool_suburl = produce_tool_suburl("rust-src", None, version, iso_date)
     url = ctx.attr.urls[0].format(tool_suburl)
 
-    tool_path = produce_tool_path("rust-src", None, ctx.attr.version)
-    archive_path = tool_path + _get_tool_extension(ctx)
+    tool_path = produce_tool_path("rust-src", version, None)
+    archive_path = tool_path + _get_tool_extension(getattr(ctx.attr, "urls", None))
     sha256 = sha256 or getattr(ctx.attr, "sha256s", {}).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or ""
     ctx.download_and_extract(
         url,
@@ -460,14 +507,38 @@
 
 rust_analyzer_toolchain(
     name = "{name}",
+    proc_macro_srv = {proc_macro_srv},
+    rustc = "{rustc}",
     rustc_srcs = "//lib/rustlib/src:rustc_srcs",
     visibility = ["//visibility:public"],
 )
 """
 
-def BUILD_for_rust_analyzer_toolchain(name):
+def BUILD_for_rust_analyzer_toolchain(name, rustc, proc_macro_srv):
     return _build_file_for_rust_analyzer_toolchain_template.format(
         name = name,
+        rustc = rustc,
+        proc_macro_srv = repr(proc_macro_srv),
+    )
+
+_build_file_for_rustfmt_toolchain_template = """\
+load("@rules_rust//rust:toolchain.bzl", "rustfmt_toolchain")
+
+rustfmt_toolchain(
+    name = "{name}",
+    rustfmt = "{rustfmt}",
+    rustc = "{rustc}",
+    rustc_lib = "{rustc_lib}",
+    visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_rustfmt_toolchain(name, rustfmt, rustc, rustc_lib):
+    return _build_file_for_rustfmt_toolchain_template.format(
+        name = name,
+        rustfmt = rustfmt,
+        rustc = rustc,
+        rustc_lib = rustc_lib,
     )
 
 def load_rust_stdlib(ctx, target_triple):
@@ -475,7 +546,7 @@
 
     Args:
         ctx (repository_ctx): A repository_ctx.
-        target_triple (str): The rust-style target triple of the tool
+        target_triple (struct): The rust-style target triple of the tool
 
     Returns:
         str: The BUILD file contents for this stdlib
@@ -486,7 +557,7 @@
         iso_date = ctx.attr.iso_date,
         target_triple = target_triple,
         tool_name = "rust-std",
-        tool_subdirectories = ["rust-std-{}".format(target_triple)],
+        tool_subdirectories = ["rust-std-{}".format(target_triple.str)],
         version = ctx.attr.version,
     )
 
@@ -550,24 +621,24 @@
     """Produces a fully qualified Rust tool name for URL
 
     Args:
-        tool_name: The name of the tool per static.rust-lang.org
-        target_triple: The rust-style target triple of the tool
-        version: The version of the tool among "nightly", "beta', or an exact version.
-        iso_date: The date of the tool (or None, if the version is a specific version).
+        tool_name (str): The name of the tool per `static.rust-lang.org`.
+        target_triple (struct): The rust-style target triple of the tool.
+        version (str): The version of the tool among "nightly", "beta', or an exact version.
+        iso_date (str): The date of the tool (or None, if the version is a specific version).
 
     Returns:
         str: The fully qualified url path for the specified tool.
     """
-    path = produce_tool_path(tool_name, target_triple, version)
+    path = produce_tool_path(tool_name, version, target_triple)
     return iso_date + "/" + path if (iso_date and version in ("beta", "nightly")) else path
 
-def produce_tool_path(tool_name, target_triple, version):
+def produce_tool_path(tool_name, version, target_triple = None):
     """Produces a qualified Rust tool name
 
     Args:
-        tool_name: The name of the tool per static.rust-lang.org
-        target_triple: The rust-style target triple of the tool
-        version: The version of the tool among "nightly", "beta', or an exact version.
+        tool_name (str): The name of the tool per static.rust-lang.org
+        version (str): The version of the tool among "nightly", "beta', or an exact version.
+        target_triple (struct, optional): The rust-style target triple of the tool
 
     Returns:
         str: The qualified path for the specified tool.
@@ -576,7 +647,37 @@
         fail("No tool name was provided")
     if not version:
         fail("No tool version was provided")
-    return "-".join([e for e in [tool_name, version, target_triple] if e])
+
+    # Not all tools require a triple. E.g. `rustc_src` (Rust source files for rust-analyzer).
+    platform_triple = None
+    if target_triple:
+        platform_triple = target_triple.str
+
+    return "-".join([e for e in [tool_name, version, platform_triple] if e])
+
+def lookup_tool_sha256(ctx, tool_name, target_triple, version, iso_date, sha256):
+    """Looks up the sha256 hash of a specific tool archive.
+
+    The lookup order is:
+
+    1. The sha256s dict in the context attributes;
+    2. The list of sha256 hashes populated in //rust:known_shas.bzl;
+    3. The sha256 argument to the function
+
+    Args:
+        ctx (repository_ctx): A repository_ctx (no attrs required).
+        tool_name (str): The name of the given tool per the archive naming.
+        target_triple (struct): The rust-style target triple of the tool.
+        version (str): The version of the tool among "nightly", "beta', or an exact version.
+        iso_date (str): The date of the tool (ignored if the version is a specific version).
+        sha256 (str): The expected hash of hash of the Rust tool.
+
+    Returns:
+        str: The sha256 of the tool archive, or an empty string if the hash could not be found.
+    """
+    tool_suburl = produce_tool_suburl(tool_name, target_triple, version, iso_date)
+    archive_path = tool_suburl + _get_tool_extension(getattr(ctx.attr, "urls", None))
+    return getattr(ctx.attr, "sha256s", dict()).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or sha256
 
 def load_arbitrary_tool(ctx, tool_name, tool_subdirectories, version, iso_date, target_triple, sha256 = ""):
     """Loads a Rust tool, downloads, and extracts into the common workspace.
@@ -603,7 +704,7 @@
             tool_subdirectories = ["clippy-preview", "rustc"]
         version (str): The version of the tool among "nightly", "beta', or an exact version.
         iso_date (str): The date of the tool (ignored if the version is a specific version).
-        target_triple (str): The rust-style target triple of the tool
+        target_triple (struct): The rust-style target triple of the tool.
         sha256 (str, optional): The expected hash of hash of the Rust tool. Defaults to "".
     """
     check_version_valid(version, iso_date, param_prefix = tool_name + "_")
@@ -618,9 +719,9 @@
         if new_url not in urls:
             urls.append(new_url)
 
-    tool_path = produce_tool_path(tool_name, target_triple, version)
-    archive_path = tool_path + _get_tool_extension(ctx)
-    sha256 = getattr(ctx.attr, "sha256s", dict()).get(archive_path) or FILE_KEY_TO_SHA.get(archive_path) or sha256
+    tool_path = produce_tool_path(tool_name, version, target_triple)
+
+    sha256 = lookup_tool_sha256(ctx, tool_name, target_triple, version, iso_date, sha256)
 
     for subdirectory in tool_subdirectories:
         # As long as the sha256 value is consistent accross calls here the
@@ -646,11 +747,118 @@
         ret[url] = auth
     return ret
 
-def _get_tool_extension(ctx):
-    urls = getattr(ctx.attr, "urls", DEFAULT_STATIC_RUST_URL_TEMPLATES)
+def _get_tool_extension(urls = None):
+    if urls == None:
+        urls = DEFAULT_STATIC_RUST_URL_TEMPLATES
     if urls[0][-7:] == ".tar.gz":
         return ".tar.gz"
     elif urls[0][-7:] == ".tar.xz":
         return ".tar.xz"
     else:
         return ""
+
+def select_rust_version(versions):
+    """Select the highest priorty version for a list of Rust versions
+
+    Priority order: `stable > nightly > beta`
+
+    Note that duplicate channels are unexpected in `versions`.
+
+    Args:
+        versions (list): A list of Rust versions. E.g. [`1.66.0`, `nightly/2022-12-15`]
+
+    Returns:
+        str: The highest ranking value from `versions`
+    """
+    if not versions:
+        fail("No versions were provided")
+
+    current = versions[0]
+
+    for ver in versions:
+        if ver.startswith("beta"):
+            if current[0].isdigit() or current.startswith("nightly"):
+                continue
+            if current.startswith("beta") and ver > current:
+                current = ver
+                continue
+
+            current = ver
+        elif ver.startswith("nightly"):
+            if current[0].isdigit():
+                continue
+            if current.startswith("nightly") and ver > current:
+                current = ver
+                continue
+
+            current = ver
+
+        else:
+            current = ver
+
+    return current
+
+_build_file_for_toolchain_hub_template = """
+toolchain(
+    name = "{name}",
+    exec_compatible_with = {exec_constraint_sets_serialized},
+    target_compatible_with = {target_constraint_sets_serialized},
+    toolchain = "{toolchain}",
+    toolchain_type = "{toolchain_type}",
+    visibility = ["//visibility:public"],
+)
+"""
+
+def BUILD_for_toolchain_hub(
+        toolchain_names,
+        toolchain_labels,
+        toolchain_types,
+        target_compatible_with,
+        exec_compatible_with):
+    return "\n".join([_build_file_for_toolchain_hub_template.format(
+        name = toolchain_name,
+        exec_constraint_sets_serialized = json.encode(exec_compatible_with[toolchain_name]),
+        target_constraint_sets_serialized = json.encode(target_compatible_with[toolchain_name]),
+        toolchain = toolchain_labels[toolchain_name],
+        toolchain_type = toolchain_types[toolchain_name],
+    ) for toolchain_name in toolchain_names])
+
+def _toolchain_repository_hub_impl(repository_ctx):
+    repository_ctx.file("WORKSPACE.bazel", """workspace(name = "{}")""".format(
+        repository_ctx.name,
+    ))
+
+    repository_ctx.file("BUILD.bazel", BUILD_for_toolchain_hub(
+        toolchain_names = repository_ctx.attr.toolchain_names,
+        toolchain_labels = repository_ctx.attr.toolchain_labels,
+        toolchain_types = repository_ctx.attr.toolchain_types,
+        target_compatible_with = repository_ctx.attr.target_compatible_with,
+        exec_compatible_with = repository_ctx.attr.exec_compatible_with,
+    ))
+
+toolchain_repository_hub = repository_rule(
+    doc = (
+        "Generates a toolchain-bearing repository that declares a set of other toolchains from other " +
+        "repositories. This exists to allow registering a set of toolchains in one go with the `:all` target."
+    ),
+    attrs = {
+        "exec_compatible_with": attr.string_list_dict(
+            doc = "A list of constraints for the execution platform for this toolchain, keyed by toolchain name.",
+            mandatory = True,
+        ),
+        "target_compatible_with": attr.string_list_dict(
+            doc = "A list of constraints for the target platform for this toolchain, keyed by toolchain name.",
+            mandatory = True,
+        ),
+        "toolchain_labels": attr.string_dict(
+            doc = "The name of the toolchain implementation target, keyed by toolchain name.",
+            mandatory = True,
+        ),
+        "toolchain_names": attr.string_list(mandatory = True),
+        "toolchain_types": attr.string_dict(
+            doc = "The toolchain type of the toolchain to declare, keyed by toolchain name.",
+            mandatory = True,
+        ),
+    },
+    implementation = _toolchain_repository_hub_impl,
+)
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index 571813b..d6f09e8 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -12,14 +12,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# buildifier: disable=module-docstring
+"""Rust rule implementations"""
+
 load("@bazel_skylib//lib:paths.bzl", "paths")
 load("//rust/private:common.bzl", "rust_common")
+load("//rust/private:providers.bzl", "BuildInfo")
 load("//rust/private:rustc.bzl", "rustc_compile_action")
 load(
     "//rust/private:utils.bzl",
     "can_build_metadata",
     "compute_crate_name",
+    "crate_root_src",
     "dedent",
     "determine_output_hash",
     "expand_dict_value_locations",
@@ -94,7 +97,7 @@
               "please file an issue!").format(crate_type))
 
     prefix = "lib"
-    if (toolchain.target_triple.find("windows") != -1) and crate_type not in ("lib", "rlib"):
+    if toolchain.target_triple and toolchain.target_os == "windows" and crate_type not in ("lib", "rlib"):
         prefix = ""
     if toolchain.target_arch == "wasm32" and crate_type == "cdylib":
         prefix = ""
@@ -151,9 +154,10 @@
     generated_sources = []
 
     generated_root = crate_root
+    package_root = paths.dirname(ctx.build_file_path)
 
     if crate_root and (crate_root.is_source or crate_root.root.path != ctx.bin_dir.path):
-        generated_root = ctx.actions.declare_file(crate_root.basename)
+        generated_root = ctx.actions.declare_file(paths.relativize(crate_root.short_path, package_root))
         ctx.actions.symlink(
             output = generated_root,
             target_file = crate_root,
@@ -167,7 +171,7 @@
         if src == crate_root:
             continue
         if src.is_source or src.root.path != ctx.bin_dir.path:
-            src_symlink = ctx.actions.declare_file(src.basename)
+            src_symlink = ctx.actions.declare_file(paths.relativize(src.short_path, package_root))
             ctx.actions.symlink(
                 output = src_symlink,
                 target_file = src,
@@ -179,48 +183,6 @@
 
     return generated_sources, generated_root
 
-def crate_root_src(name, srcs, crate_type):
-    """Determines the source file for the crate root, should it not be specified in `attr.crate_root`.
-
-    Args:
-        name (str): The name of the target.
-        srcs (list): A list of all sources for the target Crate.
-        crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
-
-    Returns:
-        File: The root File object for a given crate. See the following links for more details:
-            - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library
-            - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
-    """
-    default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
-
-    crate_root = (
-        (srcs[0] if len(srcs) == 1 else None) or
-        _shortest_src_with_basename(srcs, default_crate_root_filename) or
-        _shortest_src_with_basename(srcs, name + ".rs")
-    )
-    if not crate_root:
-        file_names = [default_crate_root_filename, name + ".rs"]
-        fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
-    return crate_root
-
-def _shortest_src_with_basename(srcs, basename):
-    """Finds the shortest among the paths in srcs that match the desired basename.
-
-    Args:
-        srcs (list): A list of File objects
-        basename (str): The target basename to match against.
-
-    Returns:
-        File: The File object with the shortest path that matches `basename`
-    """
-    shortest = None
-    for f in srcs:
-        if f.basename == basename:
-            if not shortest or len(f.dirname) < len(shortest.dirname):
-                shortest = f
-    return shortest
-
 def _rust_library_impl(ctx):
     """The implementation of the `rust_library` rule.
 
@@ -289,15 +251,16 @@
     Returns:
         list: A list of providers. See `rustc_compile_action`
     """
-
-    srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
-    if not crate_root:
-        crate_root = crate_root_src(ctx.attr.name, srcs, "lib")
     _assert_no_deprecated_attributes(ctx)
     _assert_correct_dep_mapping(ctx)
 
     toolchain = find_toolchain(ctx)
 
+    crate_root = getattr(ctx.file, "crate_root", None)
+    if not crate_root:
+        crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_type)
+    srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, crate_root)
+
     # Determine unique hash for this rlib.
     # Note that we don't include a hash for `cdylib` and `staticlib` since they are meant to be consumed externally
     # and having a deterministic name is important since it ends up embedded in the executable. This is problematic
@@ -346,6 +309,7 @@
             rustc_env_files = ctx.files.rustc_env_files,
             is_test = False,
             compile_data = depset(ctx.files.compile_data),
+            compile_data_targets = depset(ctx.attr.compile_data),
             owner = ctx.label,
         ),
         output_hash = output_hash,
@@ -369,9 +333,10 @@
     deps = transform_deps(ctx.attr.deps)
     proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
 
-    srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
+    crate_root = getattr(ctx.file, "crate_root", None)
     if not crate_root:
-        crate_root = crate_root_src(ctx.attr.name, srcs, ctx.attr.crate_type)
+        crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, ctx.attr.crate_type)
+    srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, crate_root)
 
     return rustc_compile_action(
         ctx = ctx,
@@ -391,6 +356,7 @@
             rustc_env_files = ctx.files.rustc_env_files,
             is_test = False,
             compile_data = depset(ctx.files.compile_data),
+            compile_data_targets = depset(ctx.attr.compile_data),
             owner = ctx.label,
         ),
     )
@@ -409,9 +375,7 @@
 
     toolchain = find_toolchain(ctx)
 
-    srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
     crate_type = "bin"
-
     deps = transform_deps(ctx.attr.deps)
     proc_macro_deps = transform_deps(ctx.attr.proc_macro_deps + get_import_macro_deps(ctx))
 
@@ -428,11 +392,17 @@
             ),
         )
 
+        srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, getattr(ctx.file, "crate_root", None))
+
         # Optionally join compile data
         if crate.compile_data:
             compile_data = depset(ctx.files.compile_data, transitive = [crate.compile_data])
         else:
             compile_data = depset(ctx.files.compile_data)
+        if crate.compile_data_targets:
+            compile_data_targets = depset(ctx.attr.compile_data, transitive = [crate.compile_data_targets])
+        else:
+            compile_data_targets = depset(ctx.attr.compile_data)
         rustc_env_files = ctx.files.rustc_env_files + crate.rustc_env_files
         rustc_env = dict(crate.rustc_env)
         rustc_env.update(**ctx.attr.rustc_env)
@@ -452,12 +422,17 @@
             rustc_env_files = rustc_env_files,
             is_test = True,
             compile_data = compile_data,
+            compile_data_targets = compile_data_targets,
             wrapped_crate_type = crate.type,
             owner = ctx.label,
         )
     else:
+        crate_root = getattr(ctx.file, "crate_root", None)
+
         if not crate_root:
-            crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, "lib")
+            crate_root_type = "lib" if ctx.attr.use_libtest_harness else "bin"
+            crate_root = crate_root_src(ctx.attr.name, ctx.files.srcs, crate_root_type)
+        srcs, crate_root = _transform_sources(ctx, ctx.files.srcs, crate_root)
 
         output_hash = determine_output_hash(crate_root, ctx.label)
         output = ctx.actions.declare_file(
@@ -483,6 +458,7 @@
             rustc_env_files = ctx.files.rustc_env_files,
             is_test = True,
             compile_data = depset(ctx.files.compile_data),
+            compile_data_targets = depset(ctx.attr.compile_data),
             owner = ctx.label,
         )
 
@@ -504,12 +480,55 @@
         if not toolchain.llvm_profdata:
             fail("toolchain.llvm_profdata is required if toolchain.llvm_cov is set.")
 
-        env["RUST_LLVM_COV"] = toolchain.llvm_cov.path
-        env["RUST_LLVM_PROFDATA"] = toolchain.llvm_profdata.path
+        llvm_cov_path = toolchain.llvm_cov.short_path
+        if llvm_cov_path.startswith("../"):
+            llvm_cov_path = llvm_cov_path[len("../"):]
+
+        llvm_profdata_path = toolchain.llvm_profdata.short_path
+        if llvm_profdata_path.startswith("../"):
+            llvm_profdata_path = llvm_profdata_path[len("../"):]
+
+        env["RUST_LLVM_COV"] = llvm_cov_path
+        env["RUST_LLVM_PROFDATA"] = llvm_profdata_path
+    components = "{}/{}".format(ctx.label.workspace_root, ctx.label.package).split("/")
+    env["CARGO_MANIFEST_DIR"] = "/".join([c for c in components if c])
     providers.append(testing.TestEnvironment(env))
 
     return providers
 
+def _rust_library_group_impl(ctx):
+    dep_variant_infos = []
+    dep_variant_transitive_infos = []
+    runfiles = []
+
+    for dep in ctx.attr.deps:
+        if rust_common.crate_info in dep:
+            dep_variant_infos.append(rust_common.dep_variant_info(
+                crate_info = dep[rust_common.crate_info] if rust_common.crate_info in dep else None,
+                dep_info = dep[rust_common.dep_info] if rust_common.crate_info in dep else None,
+                build_info = dep[BuildInfo] if BuildInfo in dep else None,
+                cc_info = dep[CcInfo] if CcInfo in dep else None,
+                crate_group_info = None,
+            ))
+        elif rust_common.crate_group_info in dep:
+            dep_variant_transitive_infos.append(dep[rust_common.crate_group_info].dep_variant_infos)
+        else:
+            fail("crate_group_info targets can only depend on rust_library or rust_library_group targets.")
+
+        if dep[DefaultInfo].default_runfiles != None:
+            runfiles.append(dep[DefaultInfo].default_runfiles)
+
+    return [
+        rust_common.crate_group_info(
+            dep_variant_infos = depset(dep_variant_infos, transitive = dep_variant_transitive_infos),
+        ),
+        DefaultInfo(runfiles = ctx.runfiles().merge_all(runfiles)),
+        coverage_common.instrumented_files_info(
+            ctx,
+            dependency_attributes = ["deps"],
+        ),
+    ]
+
 def _stamp_attribute(default_value):
     return attr.int(
         doc = dedent("""\
@@ -680,11 +699,6 @@
         ),
         default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
     ),
-    "_collect_cc_coverage": attr.label(
-        default = Label("//util:collect_coverage"),
-        executable = True,
-        cfg = "exec",
-    ),
     "_error_format": attr.label(
         default = Label("//:error_format"),
     ),
@@ -710,6 +724,9 @@
     "_is_proc_macro_dep_enabled": attr.label(
         default = Label("//:is_proc_macro_dep_enabled"),
     ),
+    "_per_crate_rustc_flag": attr.label(
+        default = Label("//:experimental_per_crate_rustc_flag"),
+    ),
     "_process_wrapper": attr.label(
         doc = "A process wrapper for running rustc on all platforms.",
         default = Label("//util/process_wrapper"),
@@ -723,6 +740,28 @@
     ),
 }
 
+_coverage_attrs = {
+    "_collect_cc_coverage": attr.label(
+        default = Label("//util:collect_coverage"),
+        executable = True,
+        cfg = "exec",
+    ),
+    # Bazel’s coverage runner
+    # (https://github.com/bazelbuild/bazel/blob/6.0.0/tools/test/collect_coverage.sh)
+    # needs a binary called “lcov_merge.”  Its location is passed in the
+    # LCOV_MERGER environmental variable.  For builtin rules, this variable
+    # is set automatically based on a magic “$lcov_merger” or
+    # “:lcov_merger” attribute, but it’s not possible to create such
+    # attributes in Starlark.  Therefore we specify the variable ourselves.
+    # Note that the coverage runner runs in the runfiles root instead of
+    # the execution root, therefore we use “path” instead of “short_path.”
+    "_lcov_merger": attr.label(
+        default = configuration_field(fragment = "coverage", name = "output_generator"),
+        executable = True,
+        cfg = "exec",
+    ),
+}
+
 _experimental_use_cc_common_link_attrs = {
     "experimental_use_cc_common_link": attr.int(
         doc = (
@@ -736,6 +775,25 @@
         values = [-1, 0, 1],
         default = -1,
     ),
+    "malloc": attr.label(
+        default = Label("@bazel_tools//tools/cpp:malloc"),
+        doc = """Override the default dependency on `malloc`.
+
+By default, Rust binaries linked with cc_common.link are linked against
+`@bazel_tools//tools/cpp:malloc"`, which is an empty library and the resulting binary will use
+libc's `malloc`. This label must refer to a `cc_library` rule.
+""",
+        mandatory = False,
+        providers = [[CcInfo]],
+    ),  # A late-bound attribute denoting the value of the `--custom_malloc`
+    # command line flag (or None if the flag is not provided).
+    "_custom_malloc": attr.label(
+        default = configuration_field(
+            fragment = "cpp",
+            name = "custom_malloc",
+        ),
+        providers = [[CcInfo]],
+    ),
 }
 
 _rust_test_attrs = dict({
@@ -754,10 +812,6 @@
             Specifies additional environment variables to set when the test is executed by bazel test.
             Values are subject to `$(rootpath)`, `$(execpath)`, location, and
             ["Make variable"](https://docs.bazel.build/versions/master/be/make-variables.html) substitution.
-
-            Execpath returns absolute path, and in order to be able to construct the absolute path we
-            need to wrap the test binary in a launcher. Using a launcher comes with complications, such as
-            more complicated debugger attachment.
         """),
     ),
     "use_libtest_harness": attr.bool(
@@ -775,7 +829,7 @@
         default = Label("@bazel_tools//tools/cpp:grep-includes"),
         executable = True,
     ),
-}.items() + _experimental_use_cc_common_link_attrs.items())
+}.items() + _coverage_attrs.items() + _experimental_use_cc_common_link_attrs.items())
 
 _common_providers = [
     rust_common.crate_info,
@@ -894,7 +948,16 @@
 
 rust_shared_library = rule(
     implementation = _rust_shared_library_impl,
-    attrs = dict(_common_attrs.items()),
+    attrs = dict(
+        _common_attrs.items() + _experimental_use_cc_common_link_attrs.items() + {
+            "_grep_includes": attr.label(
+                allow_single_file = True,
+                cfg = "exec",
+                default = Label("@bazel_tools//tools/cpp:grep-includes"),
+                executable = True,
+            ),
+        }.items(),
+    ),
     fragments = ["cpp"],
     host_fragments = ["cpp"],
     toolchains = [
@@ -1225,6 +1288,7 @@
             crate = ":hello_lib",
             # You may add other deps that are specific to the test configuration
             deps = ["//some/dev/dep"],
+        )
         ```
 
         Run the test with `bazel test //hello_lib:hello_lib_test`. The crate
@@ -1360,3 +1424,48 @@
         tests = tests,
         tags = kwargs.get("tags", None),
     )
+
+rust_library_group = rule(
+    implementation = _rust_library_group_impl,
+    provides = [rust_common.crate_group_info],
+    attrs = {
+        "deps": attr.label_list(
+            doc = "Other dependencies to forward through this crate group.",
+            providers = [[rust_common.crate_group_info], [rust_common.crate_info]],
+        ),
+    },
+    doc = dedent("""\
+        Functions as an alias for a set of dependencies.
+
+        Specifically, the following are equivalent:
+
+        ```starlark
+        rust_library_group(
+            name = "crate_group",
+            deps = [
+                ":crate1",
+                ":crate2",
+            ],
+        )
+
+        rust_library(
+            name = "foobar",
+            deps = [":crate_group"],
+            ...
+        )
+        ```
+
+        and
+
+        ```starlark
+        rust_library(
+            name = "foobar",
+            deps = [
+                ":crate1",
+                ":crate2",
+            ],
+            ...
+        )
+        ```
+    """),
+)
diff --git a/rust/private/rust_analyzer.bzl b/rust/private/rust_analyzer.bzl
index 7515fd7..3eea6c0 100644
--- a/rust/private/rust_analyzer.bzl
+++ b/rust/private/rust_analyzer.bzl
@@ -23,7 +23,13 @@
 load("//rust/platform:triple_mappings.bzl", "system_to_dylib_ext", "triple_to_system")
 load("//rust/private:common.bzl", "rust_common")
 load("//rust/private:rustc.bzl", "BuildInfo")
-load("//rust/private:utils.bzl", "dedent", "find_toolchain")
+load(
+    "//rust/private:utils.bzl",
+    "concat",
+    "dedent",
+    "dedup_expand_location",
+    "find_toolchain",
+)
 
 RustAnalyzerInfo = provider(
     doc = "RustAnalyzerInfo holds rust crate metadata for targets",
@@ -38,8 +44,17 @@
     },
 )
 
+RustAnalyzerGroupInfo = provider(
+    doc = "RustAnalyzerGroupInfo holds multiple RustAnalyzerInfos",
+    fields = {
+        "deps": "List[RustAnalyzerInfo]: direct dependencies",
+    },
+)
+
 def _rust_analyzer_aspect_impl(target, ctx):
-    if rust_common.crate_info not in target and rust_common.test_crate_info not in target:
+    if (rust_common.crate_info not in target and
+        rust_common.test_crate_info not in target and
+        rust_common.crate_group_info not in target):
         return []
 
     toolchain = find_toolchain(ctx)
@@ -61,14 +76,33 @@
                 build_info = dep[BuildInfo]
         dep_infos = [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.deps if RustAnalyzerInfo in dep]
 
+        group_infos = [dep[RustAnalyzerGroupInfo] for dep in ctx.rule.attr.deps if RustAnalyzerGroupInfo in dep]
+        for group_info in group_infos:
+            dep_infos.extend(group_info.deps)
+
     if hasattr(ctx.rule.attr, "proc_macro_deps"):
         dep_infos += [dep[RustAnalyzerInfo] for dep in ctx.rule.attr.proc_macro_deps if RustAnalyzerInfo in dep]
-    if hasattr(ctx.rule.attr, "crate") and ctx.rule.attr.crate != None:
-        dep_infos.append(ctx.rule.attr.crate[RustAnalyzerInfo])
-    if hasattr(ctx.rule.attr, "actual") and ctx.rule.attr.actual != None and RustAnalyzerInfo in ctx.rule.attr.actual:
-        dep_infos.append(ctx.rule.attr.actual[RustAnalyzerInfo])
 
-    crate_spec = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_crate_spec")
+        group_infos = [dep[RustAnalyzerGroupInfo] for dep in ctx.rule.attr.proc_macro_deps if RustAnalyzerGroupInfo in dep]
+        for group_info in group_infos:
+            dep_infos.extend(group_info.deps)
+
+    if hasattr(ctx.rule.attr, "crate") and ctx.rule.attr.crate != None:
+        if RustAnalyzerInfo in ctx.rule.attr.crate:
+            dep_infos.append(ctx.rule.attr.crate[RustAnalyzerInfo])
+
+        if RustAnalyzerGroupInfo in ctx.rule.attr.crate:
+            dep_infos.extend(ctx.rule.attr.crate[RustAnalyzerGroupInfo])
+
+    if hasattr(ctx.rule.attr, "actual") and ctx.rule.attr.actual != None:
+        if RustAnalyzerInfo in ctx.rule.attr.actual:
+            dep_infos.append(ctx.rule.attr.actual[RustAnalyzerInfo])
+
+        if RustAnalyzerGroupInfo in ctx.rule.attr.actual:
+            dep_infos.extend(ctx.rule.attr.actul[RustAnalyzerGroupInfo])
+
+    if rust_common.crate_group_info in target:
+        return [RustAnalyzerGroupInfo(deps = dep_infos)]
 
     if rust_common.crate_info in target:
         crate_info = target[rust_common.crate_info]
@@ -77,6 +111,8 @@
     else:
         fail("Unexpected target type: {}".format(target))
 
+    crate_spec = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_crate_spec")
+
     rust_analyzer_info = RustAnalyzerInfo(
         crate = crate_info,
         cfgs = cfgs,
@@ -184,8 +220,9 @@
 
     # TODO: The only imagined use case is an env var holding a filename in the workspace passed to a
     # macro like include_bytes!. Other use cases might exist that require more complex logic.
-    expand_targets = getattr(ctx.rule.attr, "data", []) + getattr(ctx.rule.attr, "compile_data", [])
-    crate["env"].update({k: ctx.expand_location(v, expand_targets) for k, v in info.env.items()})
+    expand_targets = concat([getattr(ctx.rule.attr, attr, []) for attr in ["data", "compile_data"]])
+
+    crate["env"].update({k: dedup_expand_location(ctx, v, expand_targets) for k, v in info.env.items()})
 
     # Omit when a crate appears to depend on itself (e.g. foo_test crates).
     # It can happen a single source file is present in multiple crates - there can
@@ -199,13 +236,15 @@
     # common and expected - `rust_test.crate` pointing to the `rust_library`.
     crate["deps"] = [_crate_id(dep.crate) for dep in info.deps if _crate_id(dep.crate) != crate_id]
     crate["cfg"] = info.cfgs
-    crate["target"] = find_toolchain(ctx).target_triple
+    crate["target"] = find_toolchain(ctx).target_triple.str
     if info.proc_macro_dylib_path != None:
         crate["proc_macro_dylib_path"] = _EXEC_ROOT_TEMPLATE + info.proc_macro_dylib_path
     return crate
 
 def _rust_analyzer_toolchain_impl(ctx):
     toolchain = platform_common.ToolchainInfo(
+        proc_macro_srv = ctx.executable.proc_macro_srv,
+        rustc = ctx.executable.rustc,
         rustc_srcs = ctx.attr.rustc_srcs,
     )
 
@@ -215,6 +254,19 @@
     implementation = _rust_analyzer_toolchain_impl,
     doc = "A toolchain for [rust-analyzer](https://rust-analyzer.github.io/).",
     attrs = {
+        "proc_macro_srv": attr.label(
+            doc = "The path to a `rust_analyzer_proc_macro_srv` binary.",
+            cfg = "exec",
+            executable = True,
+            allow_single_file = True,
+        ),
+        "rustc": attr.label(
+            doc = "The path to a `rustc` binary.",
+            cfg = "exec",
+            executable = True,
+            allow_single_file = True,
+            mandatory = True,
+        ),
         "rustc_srcs": attr.label(
             doc = "The source code of rustc.",
             mandatory = True,
@@ -238,13 +290,30 @@
     if rustc_srcs.label.workspace_root:
         sysroot_src = _OUTPUT_BASE_TEMPLATE + rustc_srcs.label.workspace_root + "/" + sysroot_src
 
-    sysroot_src_file = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_sysroot_src")
-    ctx.actions.write(
-        output = sysroot_src_file,
-        content = sysroot_src,
+    rustc = rust_analyzer_toolchain.rustc
+    sysroot_dir, _, bin_dir = rustc.dirname.rpartition("/")
+    if bin_dir != "bin":
+        fail("The rustc path is expected to be relative to the sysroot as `bin/rustc`. Instead got: {}".format(
+            rustc.path,
+        ))
+
+    sysroot = "{}/{}".format(
+        _OUTPUT_BASE_TEMPLATE,
+        sysroot_dir,
     )
 
-    return [DefaultInfo(files = depset([sysroot_src_file]))]
+    toolchain_info = {
+        "sysroot": sysroot,
+        "sysroot_src": sysroot_src,
+    }
+
+    output = ctx.actions.declare_file(ctx.label.name + ".rust_analyzer_toolchain.json")
+    ctx.actions.write(
+        output = output,
+        content = json.encode_indent(toolchain_info, indent = " " * 4),
+    )
+
+    return [DefaultInfo(files = depset([output]))]
 
 rust_analyzer_detect_sysroot = rule(
     implementation = _rust_analyzer_detect_sysroot_impl,
diff --git a/rust/private/rustc.bzl b/rust/private/rustc.bzl
index 490bfa1..9f3b0c2 100644
--- a/rust/private/rustc.bzl
+++ b/rust/private/rustc.bzl
@@ -14,9 +14,13 @@
 
 """Functionality for constructing actions that invoke the Rust compiler"""
 
+load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
 load(
     "@bazel_tools//tools/build_defs/cc:action_names.bzl",
+    "CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME",
     "CPP_LINK_EXECUTABLE_ACTION_NAME",
+    "CPP_LINK_NODEPS_DYNAMIC_LIBRARY_ACTION_NAME",
+    "CPP_LINK_STATIC_LIBRARY_ACTION_NAME",
 )
 load("//rust/private:common.bzl", "rust_common")
 load("//rust/private:providers.bzl", _BuildInfo = "BuildInfo")
@@ -62,6 +66,11 @@
     fields = {"extra_exec_rustc_flags": "List[string] Extra flags to pass to rustc in exec configuration"},
 )
 
+PerCrateRustcFlagsInfo = provider(
+    doc = "Pass each value as an additional flag to non-exec rustc invocations for crates matching the provided filter",
+    fields = {"per_crate_rustc_flags": "List[string] Extra flags to pass to rustc in non-exec configuration"},
+)
+
 IsProcMacroDepInfo = provider(
     doc = "Records if this is a transitive dependency of a proc-macro.",
     fields = {"is_proc_macro_dep": "Boolean"},
@@ -107,9 +116,10 @@
         patch, pre = patch.split("-", 1)
     else:
         pre = ""
+
     result = {
-        "CARGO_CFG_TARGET_ARCH": toolchain.target_arch,
-        "CARGO_CFG_TARGET_OS": toolchain.os,
+        "CARGO_CFG_TARGET_ARCH": "" if toolchain.target_arch == None else toolchain.target_arch,
+        "CARGO_CFG_TARGET_OS": "" if toolchain.target_os == None else toolchain.target_os,
         "CARGO_CRATE_NAME": crate_name,
         "CARGO_PKG_AUTHORS": "",
         "CARGO_PKG_DESCRIPTION": "",
@@ -153,7 +163,7 @@
             # attribute is required for compiling linkstamps.
             has_grep_includes)
 
-def _should_use_pic(cc_toolchain, feature_configuration, crate_type):
+def _should_use_pic(cc_toolchain, feature_configuration, crate_type, compilation_mode):
     """Whether or not [PIC][pic] should be enabled
 
     [pic]: https://en.wikipedia.org/wiki/Position-independent_code
@@ -162,12 +172,20 @@
         cc_toolchain (CcToolchainInfo): The current `cc_toolchain`.
         feature_configuration (FeatureConfiguration): Feature configuration to be queried.
         crate_type (str): A Rust target's crate type.
+        compilation_mode: The compilation mode.
 
     Returns:
         bool: Whether or not [PIC][pic] should be enabled.
     """
-    if crate_type in ("cdylib", "dylib"):
+
+    # We use the same logic to select between `pic` and `nopic` outputs as the C++ rules:
+    # - For shared libraries - we use `pic`. This covers `dylib`, `cdylib` and `proc-macro` crate types.
+    # - In `fastbuild` and `dbg` mode we use `pic` by default.
+    # - In `opt` mode we use `nopic` outputs to build binaries.
+    if crate_type in ("cdylib", "dylib", "proc-macro"):
         return cc_toolchain.needs_pic_for_dynamic_libraries(feature_configuration = feature_configuration)
+    elif compilation_mode in ("fastbuild", "dbg"):
+        return True
     return False
 
 def _is_proc_macro(crate_info):
@@ -203,8 +221,26 @@
     transitive_crate_outputs = []
     transitive_metadata_outputs = []
 
-    aliases = {k.label: v for k, v in aliases.items()}
+    crate_deps = []
     for dep in depset(transitive = [deps, proc_macro_deps]).to_list():
+        crate_group = None
+
+        if type(dep) == "Target" and rust_common.crate_group_info in dep:
+            crate_group = dep[rust_common.crate_group_info]
+        elif type(dep) == "struct" and hasattr(dep, "crate_group_info") and dep.crate_group_info != None:
+            crate_group = dep.crate_group_info
+        else:
+            crate_deps.append(dep)
+
+        if crate_group:
+            for dep_variant_info in crate_group.dep_variant_infos.to_list():
+                crate_deps.append(struct(
+                    crate_info = dep_variant_info.crate_info,
+                    dep_info = dep_variant_info.dep_info,
+                ))
+
+    aliases = {k.label: v for k, v in aliases.items()}
+    for dep in crate_deps:
         (crate_info, dep_info) = _get_crate_and_dep_info(dep)
         cc_info = _get_cc_info(dep)
         dep_build_info = _get_build_info(dep)
@@ -335,15 +371,17 @@
     """
     return ctx.fragments.cpp.linkopts
 
-def get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths):
+def get_linker_and_args(ctx, attr, crate_type, cc_toolchain, feature_configuration, rpaths, rustdoc = False):
     """Gathers cc_common linker information
 
     Args:
         ctx (ctx): The current target's context object
         attr (struct): Attributes to use in gathering linker args
+        crate_type (str): The target crate's type (i.e. "bin", "proc-macro", etc.).
         cc_toolchain (CcToolchain): cc_toolchain for which we are creating build variables.
         feature_configuration (FeatureConfiguration): Feature configuration to be queried.
         rpaths (depset): Depset of directories where loader will look for libraries at runtime.
+        rustdoc (bool, optional): Whether to add "bin" link flags to the command regardless of `crate_type`.
 
 
     Returns:
@@ -354,6 +392,24 @@
     """
     user_link_flags = get_cc_user_link_flags(ctx)
 
+    if crate_type in ("bin") or rustdoc:
+        is_linking_dynamic_library = False
+        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME
+    elif crate_type in ("dylib"):
+        is_linking_dynamic_library = True
+        action_name = CPP_LINK_NODEPS_DYNAMIC_LIBRARY_ACTION_NAME
+    elif crate_type in ("staticlib"):
+        is_linking_dynamic_library = False
+        action_name = CPP_LINK_STATIC_LIBRARY_ACTION_NAME
+    elif crate_type in ("cdylib", "proc-macro"):
+        # Proc macros get compiled as shared libraries to be loaded by the compiler.
+        is_linking_dynamic_library = True
+        action_name = CPP_LINK_DYNAMIC_LIBRARY_ACTION_NAME
+    elif crate_type in ("lib", "rlib"):
+        fail("Invalid `crate_type` for linking action: {}".format(crate_type))
+    else:
+        fail("Unknown `crate_type`: {}".format(crate_type))
+
     # Add linkopt's from dependencies. This includes linkopts from transitive
     # dependencies since they get merged up.
     for dep in getattr(attr, "deps", []):
@@ -364,23 +420,23 @@
     link_variables = cc_common.create_link_variables(
         feature_configuration = feature_configuration,
         cc_toolchain = cc_toolchain,
-        is_linking_dynamic_library = False,
+        is_linking_dynamic_library = is_linking_dynamic_library,
         runtime_library_search_directories = rpaths,
         user_link_flags = user_link_flags,
     )
     link_args = cc_common.get_memory_inefficient_command_line(
         feature_configuration = feature_configuration,
-        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+        action_name = action_name,
         variables = link_variables,
     )
     link_env = cc_common.get_environment_variables(
         feature_configuration = feature_configuration,
-        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+        action_name = action_name,
         variables = link_variables,
     )
     ld = cc_common.get_tool_for_action(
         feature_configuration = feature_configuration,
-        action_name = CPP_LINK_EXECUTABLE_ACTION_NAME,
+        action_name = action_name,
     )
 
     return ld, link_args, link_env
@@ -426,13 +482,19 @@
 
     # Take the absolute value of hash() since it could be negative.
     path_hash = abs(hash(lib.path))
-    lib_name = get_lib_name_for_windows(lib) if toolchain.os.startswith("windows") else get_lib_name_default(lib)
+    lib_name = get_lib_name_for_windows(lib) if toolchain.target_os.startswith("windows") else get_lib_name_default(lib)
 
-    prefix = "lib"
-    extension = ".a"
-    if toolchain.os.startswith("windows"):
+    if toolchain.target_os.startswith("windows"):
         prefix = ""
         extension = ".lib"
+    elif lib_name.endswith(".pic"):
+        # Strip the .pic suffix
+        lib_name = lib_name[:-4]
+        prefix = "lib"
+        extension = ".pic.a"
+    else:
+        prefix = "lib"
+        extension = ".a"
 
     # Ensure the symlink follows the lib<name>.a pattern on Unix-like platforms
     # or <name>.lib on Windows.
@@ -489,7 +551,7 @@
             if _is_dylib(lib):
                 continue
             artifact = get_preferred_artifact(lib, use_pic)
-            name = get_lib_name_for_windows(artifact) if toolchain.os.startswith("windows") else get_lib_name_default(artifact)
+            name = get_lib_name_for_windows(artifact) if toolchain.target_os.startswith("windows") else get_lib_name_default(artifact)
 
             # On Linux-like platforms, normally library base names start with
             # `lib`, following the pattern `lib[name].(a|lo)` and we pass
@@ -499,10 +561,10 @@
             # FIXME: Under the native-link-modifiers unstable rustc feature,
             # we could use -lstatic:+verbatim instead.
             needs_symlink_to_standardize_name = (
-                (toolchain.os.startswith("linux") or toolchain.os.startswith("mac") or toolchain.os.startswith("darwin")) and
+                toolchain.target_os.startswith(("linux", "mac", "darwin")) and
                 artifact.basename.endswith(".a") and not artifact.basename.startswith("lib")
             ) or (
-                toolchain.os.startswith("windows") and not artifact.basename.endswith(".lib")
+                toolchain.target_os.startswith("windows") and not artifact.basename.endswith(".lib")
             )
 
             # Detect cases where we need to disambiguate library dependencies
@@ -591,20 +653,16 @@
     linker_script = getattr(file, "linker_script") if hasattr(file, "linker_script") else None
 
     linker_depset = cc_toolchain.all_files
+    compilation_mode = ctx.var["COMPILATION_MODE"]
 
-    use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
+    use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type, compilation_mode)
 
     # Pass linker inputs only for linking-like actions, not for example where
     # the output is rlib. This avoids quadratic behavior where transitive noncrates are
     # flattened on each transitive rust_library dependency.
     additional_transitive_inputs = []
     ambiguous_libs = {}
-    if crate_info.type in ("staticlib", "proc-macro"):
-        additional_transitive_inputs = _collect_libs_from_linker_inputs(
-            dep_info.transitive_noncrates.to_list(),
-            use_pic,
-        )
-    elif crate_info.type in ("bin", "dylib", "cdylib"):
+    if crate_info.type not in ("lib", "rlib"):
         linker_inputs = dep_info.transitive_noncrates.to_list()
         ambiguous_libs = _disambiguate_libs(ctx.actions, toolchain, crate_info, dep_info, use_pic)
         additional_transitive_inputs = _collect_libs_from_linker_inputs(linker_inputs, use_pic) + [
@@ -706,9 +764,9 @@
         build_flags_files,
         emit = ["dep-info", "link"],
         force_all_deps_direct = False,
-        force_link = False,
+        rustdoc = False,
         stamp = False,
-        remap_path_prefix = ".",
+        remap_path_prefix = "",
         use_json_output = False,
         build_metadata = False,
         force_depend_on_objects = False):
@@ -734,10 +792,10 @@
         emit (list): Values for the --emit flag to rustc.
         force_all_deps_direct (bool, optional): Whether to pass the transitive rlibs with --extern
             to the commandline as opposed to -L.
-        force_link (bool, optional): Whether to add link flags to the command regardless of `emit`.
+        rustdoc (bool, optional): Whether to add "bin" link flags to the command regardless of `emit` and `crate_type`.
         stamp (bool, optional): Whether or not workspace status stamping is enabled. For more details see
             https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
-        remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to a falsey value, no prefix will be set.
+        remap_path_prefix (str, optional): A value used to remap `${pwd}` to. If set to None, no prefix will be set.
         use_json_output (bool): Have rustc emit json and process_wrapper parse json messages to output rendered output.
         build_metadata (bool): Generate CLI arguments for building *only* .rmeta files. This requires use_json_output.
         force_depend_on_objects (bool): Force using `.rlib` object files instead of metadata (`.rmeta`) files even if they are available.
@@ -840,7 +898,7 @@
         json = ["artifacts"]
         if error_format == "short":
             json.append("diagnostic-short")
-        elif error_format == "human" and toolchain.os != "windows":
+        elif error_format == "human" and toolchain.target_os != "windows":
             # If the os is not windows, we can get colorized output.
             json.append("diagnostic-rendered-ansi")
 
@@ -874,7 +932,7 @@
     rustc_flags.add("--codegen=debuginfo=" + compilation_mode.debug_info)
 
     # For determinism to help with build distribution and such
-    if remap_path_prefix:
+    if remap_path_prefix != None:
         rustc_flags.add("--remap-path-prefix=${{pwd}}={}".format(remap_path_prefix))
 
     if emit:
@@ -895,8 +953,9 @@
     rustc_flags.add_all(rust_std_paths, before_each = "-L", format_each = "%s")
     rustc_flags.add_all(rust_flags)
 
+    # Gather data path from crate_info since it is inherited from real crate for rust_doc and rust_test
     # Deduplicate data paths due to https://github.com/bazelbuild/bazel/issues/14681
-    data_paths = depset(direct = getattr(attr, "data", []) + getattr(attr, "compile_data", [])).to_list()
+    data_paths = depset(direct = getattr(attr, "data", []), transitive = [crate_info.compile_data_targets]).to_list()
 
     rustc_flags.add_all(
         expand_list_element_locations(
@@ -908,21 +967,24 @@
     add_edition_flags(rustc_flags, crate_info)
 
     # Link!
-    if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or force_link:
+    if ("link" in emit and crate_info.type not in ["rlib", "lib"]) or rustdoc:
         # Rust's built-in linker can handle linking wasm files. We don't want to attempt to use the cc
         # linker since it won't understand.
+        compilation_mode = ctx.var["COMPILATION_MODE"]
         if toolchain.target_arch != "wasm32":
             if output_dir:
-                use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type)
+                use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_info.type, compilation_mode)
                 rpaths = _compute_rpaths(toolchain, output_dir, dep_info, use_pic)
             else:
                 rpaths = depset([])
-            ld, link_args, link_env = get_linker_and_args(ctx, attr, cc_toolchain, feature_configuration, rpaths)
+
+            ld, link_args, link_env = get_linker_and_args(ctx, attr, crate_info.type, cc_toolchain, feature_configuration, rpaths, rustdoc)
+
             env.update(link_env)
             rustc_flags.add("--codegen=linker=" + ld)
             rustc_flags.add_joined("--codegen", link_args, join_with = " ", format_joined = "link-args=%s")
 
-        _add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, ambiguous_libs, crate_info.type, toolchain, cc_toolchain, feature_configuration)
+        _add_native_link_flags(rustc_flags, dep_info, linkstamp_outs, ambiguous_libs, crate_info.type, toolchain, cc_toolchain, feature_configuration, compilation_mode)
 
     use_metadata = _depend_on_metadata(crate_info, force_depend_on_objects)
 
@@ -962,6 +1024,11 @@
     if toolchain._rename_first_party_crates:
         env["RULES_RUST_THIRD_PARTY_DIR"] = toolchain._third_party_dir
 
+    if is_exec_configuration(ctx):
+        rustc_flags.add_all(toolchain.extra_exec_rustc_flags)
+    else:
+        rustc_flags.add_all(toolchain.extra_rustc_flags)
+
     # extra_rustc_flags apply to the target configuration, not the exec configuration.
     if hasattr(ctx.attr, "_extra_rustc_flags") and not is_exec_configuration(ctx):
         rustc_flags.add_all(ctx.attr._extra_rustc_flags[ExtraRustcFlagsInfo].extra_rustc_flags)
@@ -969,12 +1036,19 @@
     if hasattr(ctx.attr, "_extra_rustc_flag") and not is_exec_configuration(ctx):
         rustc_flags.add_all(ctx.attr._extra_rustc_flag[ExtraRustcFlagsInfo].extra_rustc_flags)
 
+    if hasattr(ctx.attr, "_per_crate_rustc_flag") and not is_exec_configuration(ctx):
+        per_crate_rustc_flags = ctx.attr._per_crate_rustc_flag[PerCrateRustcFlagsInfo].per_crate_rustc_flags
+        _add_per_crate_rustc_flags(ctx, rustc_flags, crate_info, per_crate_rustc_flags)
+
     if hasattr(ctx.attr, "_extra_exec_rustc_flags") and is_exec_configuration(ctx):
         rustc_flags.add_all(ctx.attr._extra_exec_rustc_flags[ExtraExecRustcFlagsInfo].extra_exec_rustc_flags)
 
     if hasattr(ctx.attr, "_extra_exec_rustc_flag") and is_exec_configuration(ctx):
         rustc_flags.add_all(ctx.attr._extra_exec_rustc_flag[ExtraExecRustcFlagsInfo].extra_exec_rustc_flags)
 
+    if _is_no_std(ctx, toolchain, crate_info):
+        rustc_flags.add_all(['--cfg=feature="no_std"'])
+
     # Create a struct which keeps the arguments separate so each may be tuned or
     # replaced where necessary
     args = struct(
@@ -1143,7 +1217,7 @@
     # For a cdylib that might be added as a dependency to a cc_* target on Windows, it is important to include the
     # interface library that rustc generates in the output files.
     interface_library = None
-    if toolchain.os == "windows" and crate_info.type == "cdylib":
+    if toolchain.target_os == "windows" and crate_info.type == "cdylib":
         # Rustc generates the import library with a `.dll.lib` extension rather than the usual `.lib` one that msvc
         # expects (see https://github.com/rust-lang/rust/pull/29520 for more context).
         interface_library = ctx.actions.declare_file(crate_info.output.basename + ".lib", sibling = crate_info.output)
@@ -1156,11 +1230,11 @@
     # types that benefit from having debug information in a separate file.
     pdb_file = None
     dsym_folder = None
-    if crate_info.type in ("cdylib", "bin") and not crate_info.is_test:
-        if toolchain.os == "windows":
+    if crate_info.type in ("cdylib", "bin"):
+        if toolchain.target_os == "windows":
             pdb_file = ctx.actions.declare_file(crate_info.output.basename[:-len(crate_info.output.extension)] + "pdb", sibling = crate_info.output)
             action_outputs.append(pdb_file)
-        elif toolchain.os == "darwin":
+        elif toolchain.target_os == "darwin":
             dsym_folder = ctx.actions.declare_directory(crate_info.output.basename + ".dSYM", sibling = crate_info.output)
             action_outputs.append(dsym_folder)
 
@@ -1223,8 +1297,14 @@
             pic_objects = depset([output_o]),
         )
 
+        malloc_library = ctx.attr._custom_malloc or ctx.attr.malloc
+
         # Collect the linking contexts of the standard library and dependencies.
-        linking_contexts = [toolchain.libstd_and_allocator_ccinfo.linking_context, toolchain.stdlib_linkflags.linking_context]
+        linking_contexts = [
+            malloc_library[CcInfo].linking_context,
+            _get_std_and_alloc_info(ctx, toolchain, crate_info).linking_context,
+            toolchain.stdlib_linkflags.linking_context,
+        ]
 
         for dep in crate_info.deps.to_list():
             if dep.cc_info:
@@ -1247,6 +1327,25 @@
 
         output_relative_to_package = crate_info.output.path[len(package_dir):]
 
+        # Compile actions that produce shared libraries create output of the form "libfoo.so" for linux and macos;
+        # cc_common.link expects us to pass "foo" to the name parameter. We cannot simply use crate_info.name because
+        # the name of the crate does not always match the name of output file, e.g a crate named foo-bar will produce
+        # a (lib)foo_bar output file.
+        if crate_info.type == "cdylib":
+            output_lib = crate_info.output.basename
+            if toolchain.target_os != "windows":
+                # Strip the leading "lib" prefix
+                output_lib = output_lib[3:]
+
+            # Strip the file extension
+            output_lib = output_lib[:-(1 + len(crate_info.output.extension))]
+
+            # Remove the basename (which contains the undesired 'lib' prefix and the file extension)
+            output_relative_to_package = output_relative_to_package[:-len(crate_info.output.basename)]
+
+            # Append the name of the library
+            output_relative_to_package = output_relative_to_package + output_lib
+
         cc_common.link(
             actions = ctx.actions,
             feature_configuration = feature_configuration,
@@ -1256,6 +1355,7 @@
             name = output_relative_to_package,
             grep_includes = ctx.file._grep_includes,
             stamp = ctx.attr.stamp,
+            output_type = "executable" if crate_info.type == "bin" else "dynamic_library",
         )
 
         outputs = [crate_info.output]
@@ -1268,6 +1368,9 @@
         files = getattr(ctx.files, "data", []) + coverage_runfiles,
         collect_data = True,
     )
+    if getattr(ctx.attr, "crate", None):
+        runfiles = runfiles.merge(ctx.attr.crate[DefaultInfo].default_runfiles)
+        runfiles = runfiles.merge(ctx.attr.crate[DefaultInfo].data_runfiles)
 
     # TODO: Remove after some resolution to
     # https://github.com/bazelbuild/rules_rust/issues/771
@@ -1303,9 +1406,29 @@
         providers.append(OutputGroupInfo(pdb_file = depset([pdb_file])))
     if dsym_folder:
         providers.append(OutputGroupInfo(dsym_folder = depset([dsym_folder])))
+    if build_metadata:
+        providers.append(OutputGroupInfo(build_metadata = depset([build_metadata])))
 
     return providers
 
+def _is_no_std(ctx, toolchain, crate_info):
+    if is_exec_configuration(ctx) or crate_info.is_test:
+        return False
+    if toolchain._no_std == "off":
+        return False
+    return True
+
+def _get_std_and_alloc_info(ctx, toolchain, crate_info):
+    if is_exec_configuration(ctx):
+        return toolchain.libstd_and_allocator_ccinfo
+    if toolchain._experimental_use_global_allocator:
+        if _is_no_std(ctx, toolchain, crate_info):
+            return toolchain.nostd_and_global_allocator_cc_info
+        else:
+            return toolchain.libstd_and_global_allocator_ccinfo
+    else:
+        return toolchain.libstd_and_allocator_ccinfo
+
 def _is_dylib(dep):
     return not bool(dep.static_library or dep.pic_static_library)
 
@@ -1403,23 +1526,26 @@
         toolchain.stdlib_linkflags,
     ]
 
-    for dep in getattr(attr, "deps", []):
-        if CcInfo in dep:
+    # Flattening is okay since crate_info.deps only records direct deps.
+    for dep in crate_info.deps.to_list():
+        if dep.cc_info:
             # A Rust staticlib or shared library doesn't need to propagate linker inputs
             # of its dependencies, except for shared libraries.
             if crate_info.type in ["cdylib", "staticlib"]:
-                shared_linker_inputs = _collect_nonstatic_linker_inputs(dep[CcInfo])
+                shared_linker_inputs = _collect_nonstatic_linker_inputs(dep.cc_info)
                 if shared_linker_inputs:
                     linking_context = cc_common.create_linking_context(
                         linker_inputs = depset(shared_linker_inputs),
                     )
                     cc_infos.append(CcInfo(linking_context = linking_context))
             else:
-                cc_infos.append(dep[CcInfo])
+                cc_infos.append(dep.cc_info)
 
-    if crate_info.type in ("rlib", "lib") and toolchain.libstd_and_allocator_ccinfo:
-        # TODO: if we already have an rlib in our deps, we could skip this
-        cc_infos.append(toolchain.libstd_and_allocator_ccinfo)
+    if crate_info.type in ("rlib", "lib"):
+        libstd_and_allocator_cc_info = _get_std_and_alloc_info(ctx, toolchain, crate_info)
+        if libstd_and_allocator_cc_info:
+            # TODO: if we already have an rlib in our deps, we could skip this
+            cc_infos.append(libstd_and_allocator_cc_info)
 
     return [cc_common.merge_cc_infos(cc_infos = cc_infos)]
 
@@ -1485,7 +1611,7 @@
 
     # Windows has no rpath equivalent, so always return an empty depset.
     # Fuchsia assembles shared libraries during packaging.
-    if toolchain.os == "windows" or toolchain.os == "fuchsia":
+    if toolchain.target_os == "windows" or toolchain.target_os == "fuchsia":
         return depset([])
 
     dylibs = [
@@ -1501,9 +1627,9 @@
     # without a version of Bazel that includes
     # https://github.com/bazelbuild/bazel/pull/13427. This is known to not be
     # included in Bazel 4.1 and below.
-    if toolchain.os != "linux" and toolchain.os != "darwin":
+    if toolchain.target_os != "linux" and toolchain.target_os != "darwin":
         fail("Runtime linking is not supported on {}, but found {}".format(
-            toolchain.os,
+            toolchain.target_os,
             dep_info.transitive_noncrates,
         ))
 
@@ -1608,7 +1734,7 @@
     """
     return crate.output.dirname
 
-def _portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name, for_windows):
+def _portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name, for_windows = False, for_darwin = False, flavor_msvc = False):
     artifact = get_preferred_artifact(lib, use_pic)
     if ambiguous_libs and artifact.path in ambiguous_libs:
         artifact = ambiguous_libs[artifact.path]
@@ -1646,11 +1772,24 @@
             artifact.basename.startswith("libtest-") or artifact.basename.startswith("libstd-") or
             artifact.basename.startswith("test-") or artifact.basename.startswith("std-")
         ):
-            return ["-lstatic=%s" % get_lib_name(artifact)]
-        return [
-            "-lstatic=%s" % get_lib_name(artifact),
-            "-Clink-arg=-l%s" % (get_lib_name(artifact) if not for_windows else artifact.basename),
-        ]
+            return [] if for_darwin else ["-lstatic=%s" % get_lib_name(artifact)]
+
+        if for_windows:
+            if flavor_msvc:
+                return [
+                    "-lstatic=%s" % get_lib_name(artifact),
+                    "-Clink-arg={}".format(artifact.basename),
+                ]
+            else:
+                return [
+                    "-lstatic=%s" % get_lib_name(artifact),
+                    "-Clink-arg=-l{}".format(artifact.basename),
+                ]
+        else:
+            return [
+                "-lstatic=%s" % get_lib_name(artifact),
+                "-Clink-arg=-l{}".format(get_lib_name(artifact)),
+            ]
     elif _is_dylib(lib):
         return [
             "-ldylib=%s" % get_lib_name(artifact),
@@ -1658,16 +1797,32 @@
 
     return []
 
-def _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs):
+def _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs, flavor_msvc):
     linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
     ret = []
     for lib in linker_input.libraries:
         if lib.alwayslink:
-            ret.extend(["-C", "link-arg=/WHOLEARCHIVE:%s" % get_preferred_artifact(lib, use_pic).path])
+            if flavor_msvc:
+                ret.extend(["-C", "link-arg=/WHOLEARCHIVE:%s" % get_preferred_artifact(lib, use_pic).path])
+            else:
+                ret.extend([
+                    "-C",
+                    "link-arg=-Wl,--whole-archive",
+                    "-C",
+                    ("link-arg=%s" % get_preferred_artifact(lib, use_pic).path),
+                    "-C",
+                    "link-arg=-Wl,--no-whole-archive",
+                ])
         else:
-            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_for_windows, for_windows = True))
+            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_for_windows, for_windows = True, flavor_msvc = flavor_msvc))
     return ret
 
+def _make_link_flags_windows_msvc(linker_input_and_use_pic_and_ambiguous_libs):
+    return _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs, flavor_msvc = True)
+
+def _make_link_flags_windows_gnu(linker_input_and_use_pic_and_ambiguous_libs):
+    return _make_link_flags_windows(linker_input_and_use_pic_and_ambiguous_libs, flavor_msvc = False)
+
 def _make_link_flags_darwin(linker_input_and_use_pic_and_ambiguous_libs):
     linker_input, use_pic, ambiguous_libs = linker_input_and_use_pic_and_ambiguous_libs
     ret = []
@@ -1678,7 +1833,7 @@
                 ("link-arg=-Wl,-force_load,%s" % get_preferred_artifact(lib, use_pic).path),
             ])
         else:
-            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_windows = False))
+            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_darwin = True))
     return ret
 
 def _make_link_flags_default(linker_input_and_use_pic_and_ambiguous_libs):
@@ -1695,7 +1850,7 @@
                 "link-arg=-Wl,--no-whole-archive",
             ])
         else:
-            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default, for_windows = False))
+            ret.extend(_portable_link_flags(lib, use_pic, ambiguous_libs, get_lib_name_default))
     return ret
 
 def _libraries_dirnames(linker_input_and_use_pic_and_ambiguous_libs):
@@ -1704,7 +1859,7 @@
     # De-duplicate names.
     return depset([get_preferred_artifact(lib, use_pic).dirname for lib in link_input.libraries]).to_list()
 
-def _add_native_link_flags(args, dep_info, linkstamp_outs, ambiguous_libs, crate_type, toolchain, cc_toolchain, feature_configuration):
+def _add_native_link_flags(args, dep_info, linkstamp_outs, ambiguous_libs, crate_type, toolchain, cc_toolchain, feature_configuration, compilation_mode):
     """Adds linker flags for all dependencies of the current target.
 
     Args:
@@ -1716,16 +1871,17 @@
         toolchain (rust_toolchain): The current `rust_toolchain`
         cc_toolchain (CcToolchainInfo): The current `cc_toolchain`
         feature_configuration (FeatureConfiguration): feature configuration to use with cc_toolchain
+        compilation_mode (bool): The compilation mode for this build.
     """
     if crate_type in ["lib", "rlib"]:
         return
 
-    use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_type)
+    use_pic = _should_use_pic(cc_toolchain, feature_configuration, crate_type, compilation_mode)
 
-    if toolchain.os == "windows":
-        make_link_flags = _make_link_flags_windows
+    if toolchain.target_os == "windows":
+        make_link_flags = _make_link_flags_windows_msvc if toolchain.target_triple.abi == "msvc" else _make_link_flags_windows_gnu
         get_lib_name = get_lib_name_for_windows
-    elif toolchain.os.startswith("mac") or toolchain.os.startswith("darwin"):
+    elif toolchain.target_os.startswith(("mac", "darwin", "ios")):
         make_link_flags = _make_link_flags_darwin
         get_lib_name = get_lib_name_default
     else:
@@ -1784,6 +1940,32 @@
     """
     return file.dirname
 
+def _add_per_crate_rustc_flags(ctx, args, crate_info, per_crate_rustc_flags):
+    """Adds matching per-crate rustc flags to an arguments object reference
+
+    Args:
+        ctx (ctx): The source rule's context object
+        args (Args): A reference to an Args object
+        crate_info (CrateInfo): A CrateInfo provider
+        per_crate_rustc_flags (list): A list of per_crate_rustc_flag values
+    """
+    for per_crate_rustc_flag in per_crate_rustc_flags:
+        at_index = per_crate_rustc_flag.find("@")
+        if at_index == -1:
+            fail("per_crate_rustc_flag '{}' does not follow the expected format: prefix_filter@flag".format(per_crate_rustc_flag))
+
+        prefix_filter = per_crate_rustc_flag[:at_index]
+        flag = per_crate_rustc_flag[at_index + 1:]
+        if not flag:
+            fail("per_crate_rustc_flag '{}' does not follow the expected format: prefix_filter@flag".format(per_crate_rustc_flag))
+
+        label_string = str(ctx.label)
+        label = label_string[1:] if label_string.startswith("@//") else label_string
+        execution_path = crate_info.root.path
+
+        if label.startswith(prefix_filter) or execution_path.startswith(prefix_filter):
+            args.add(flag)
+
 def _error_format_impl(ctx):
     """Implementation of the `error_format` rule
 
@@ -1862,3 +2044,35 @@
     implementation = _extra_exec_rustc_flag_impl,
     build_setting = config.string(flag = True, allow_multiple = True),
 )
+
+def _per_crate_rustc_flag_impl(ctx):
+    return PerCrateRustcFlagsInfo(per_crate_rustc_flags = [f for f in ctx.build_setting_value if f != ""])
+
+per_crate_rustc_flag = rule(
+    doc = (
+        "Add additional rustc_flag to matching crates from the command line with `--@rules_rust//:experimental_per_crate_rustc_flag`. " +
+        "The expected flag format is prefix_filter@flag, where any crate with a label or execution path starting with the prefix filter will be built with the given flag." +
+        "The label matching uses the canonical form of the label (i.e //package:label_name)." +
+        "The execution path is the relative path to your workspace directory including the base name (including extension) of the crate root." +
+        "This flag is only applied to the exec configuration (proc-macros, cargo_build_script, etc)." +
+        "Multiple uses are accumulated."
+    ),
+    implementation = _per_crate_rustc_flag_impl,
+    build_setting = config.string(flag = True, allow_multiple = True),
+)
+
+def _no_std_impl(ctx):
+    value = str(ctx.attr._no_std[BuildSettingInfo].value)
+    if is_exec_configuration(ctx):
+        return [config_common.FeatureFlagInfo(value = "off")]
+    return [config_common.FeatureFlagInfo(value = value)]
+
+no_std = rule(
+    doc = (
+        "No std; we need this so that we can distinguish between host and exec"
+    ),
+    attrs = {
+        "_no_std": attr.label(default = "//:no_std"),
+    },
+    implementation = _no_std_impl,
+)
diff --git a/rust/private/rustdoc.bzl b/rust/private/rustdoc.bzl
index ba3a12c..f06d18e 100644
--- a/rust/private/rustdoc.bzl
+++ b/rust/private/rustdoc.bzl
@@ -43,6 +43,7 @@
         rustc_env_files = crate_info.rustc_env_files,
         is_test = crate_info.is_test,
         compile_data = crate_info.compile_data,
+        compile_data_targets = crate_info.compile_data_targets,
     )
 
 def rustdoc_compile_action(
@@ -121,7 +122,7 @@
         build_flags_files = build_flags_files,
         emit = [],
         remap_path_prefix = None,
-        force_link = True,
+        rustdoc = True,
         force_depend_on_objects = is_test,
     )
 
@@ -131,6 +132,8 @@
     if is_test:
         if "SYSROOT" in env:
             env.update({"SYSROOT": "${{pwd}}/{}".format(toolchain.sysroot_short_path)})
+        if "OUT_DIR" in env:
+            env.update({"OUT_DIR": "${{pwd}}/{}".format(build_info.out_dir.short_path)})
 
         # `rustdoc` does not support the SYSROOT environment variable. To account
         # for this, the flag must be explicitly passed to the `rustdoc` binary.
@@ -175,6 +178,12 @@
         ctx (ctx): The rule's context object
     """
 
+    if ctx.attr.rustc_flags:
+        # buildifier: disable=print
+        print("rustc_flags is deprecated in favor of `rustdoc_flags` for rustdoc targets. Please update {}".format(
+            ctx.label,
+        ))
+
     crate = ctx.attr.crate
     crate_info = crate[rust_common.crate_info]
 
@@ -186,6 +195,8 @@
         "{}={}".format(crate_info.name, crate_info.output.path),
     ]
 
+    rustdoc_flags.extend(ctx.attr.rustdoc_flags)
+
     action = rustdoc_compile_action(
         ctx = ctx,
         toolchain = find_toolchain(ctx),
@@ -210,7 +221,7 @@
 
     return [
         DefaultInfo(
-            files = depset([ctx.outputs.rust_doc_zip]),
+            files = depset([output_dir]),
         ),
         OutputGroupInfo(
             rustdoc_dir = depset([output_dir]),
@@ -287,8 +298,11 @@
             allow_files = [".css"],
         ),
         "rustc_flags": attr.string_list(
+            doc = "**Deprecated**: use `rustdoc_flags` instead",
+        ),
+        "rustdoc_flags": attr.string_list(
             doc = dedent("""\
-                List of compiler flags passed to `rustc`.
+                List of flags passed to `rustdoc`.
 
                 These strings are subject to Make variable expansion for predefined
                 source/output path variables like `$location`, `$execpath`, and
diff --git a/rust/private/rustdoc_test.bzl b/rust/private/rustdoc_test.bzl
index 525838e..9fb73e1 100644
--- a/rust/private/rustdoc_test.bzl
+++ b/rust/private/rustdoc_test.bzl
@@ -64,6 +64,9 @@
     # Collect and dedupe all of the file roots in a list before appending
     # them to args to prevent generating a large amount of identical args
     roots = []
+    root = crate_info.output.root.path
+    if not root in roots:
+        roots.append(root)
     for dep in crate_info.deps.to_list():
         dep_crate_info = getattr(dep, "crate_info", None)
         dep_dep_info = getattr(dep, "dep_info", None)
@@ -115,18 +118,19 @@
         srcs = crate.srcs,
         deps = depset(deps, transitive = [crate.deps]),
         proc_macro_deps = crate.proc_macro_deps,
-        aliases = {},
+        aliases = crate.aliases,
         output = crate.output,
         edition = crate.edition,
         rustc_env = crate.rustc_env,
         rustc_env_files = crate.rustc_env_files,
         is_test = True,
         compile_data = crate.compile_data,
+        compile_data_targets = crate.compile_data_targets,
         wrapped_crate_type = crate.type,
         owner = ctx.label,
     )
 
-    if toolchain.os == "windows":
+    if toolchain.target_os == "windows":
         test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.bat")
     else:
         test_runner = ctx.actions.declare_file(ctx.label.name + ".rustdoc_test.sh")
diff --git a/rust/private/rustfmt.bzl b/rust/private/rustfmt.bzl
index db5ef43..a7e7100 100644
--- a/rust/private/rustfmt.bzl
+++ b/rust/private/rustfmt.bzl
@@ -1,27 +1,42 @@
 """A module defining rustfmt rules"""
 
 load(":common.bzl", "rust_common")
-load(":utils.bzl", "find_toolchain")
 
-def _find_rustfmtable_srcs(target, aspect_ctx = None):
-    """Parse a target for rustfmt formattable sources.
+def _get_rustfmt_ready_crate_info(target):
+    """Check that a target is suitable for rustfmt and extract the `CrateInfo` provider from it.
 
     Args:
         target (Target): The target the aspect is running on.
+
+    Returns:
+        CrateInfo, optional: A `CrateInfo` provider if clippy should be run or `None`.
+    """
+
+    # Ignore external targets
+    if target.label.workspace_root.startswith("external"):
+        return None
+
+    # Obviously ignore any targets that don't contain `CrateInfo`
+    if rust_common.crate_info in target:
+        return target[rust_common.crate_info]
+    elif rust_common.test_crate_info in target:
+        return target[rust_common.test_crate_info].crate
+    else:
+        return None
+
+def _find_rustfmtable_srcs(crate_info, aspect_ctx = None):
+    """Parse a `CrateInfo` provider for rustfmt formattable sources.
+
+    Args:
+        crate_info (CrateInfo): A `CrateInfo` provider.
         aspect_ctx (ctx, optional): The aspect's context object.
 
     Returns:
         list: A list of formattable sources (`File`).
     """
-    if rust_common.crate_info not in target:
-        return []
 
-    # Ignore external targets
-    if target.label.workspace_root.startswith("external"):
-        return []
-
+    # Targets with specific tags will not be formatted
     if aspect_ctx:
-        # Targets with specifc tags will not be formatted
         ignore_tags = [
             "no-format",
             "no-rustfmt",
@@ -32,8 +47,6 @@
             if tag in aspect_ctx.rule.attr.tags:
                 return []
 
-    crate_info = target[rust_common.crate_info]
-
     # Filter out any generated files
     srcs = [src for src in crate_info.srcs.to_list() if src.is_source]
 
@@ -55,7 +68,8 @@
     return manifest
 
 def _perform_check(edition, srcs, ctx):
-    toolchain = find_toolchain(ctx)
+    rustfmt_toolchain = ctx.toolchains[Label("//rust/rustfmt:toolchain_type")]
+
     config = ctx.file._config
     marker = ctx.actions.declare_file(ctx.label.name + ".rustfmt.ok")
 
@@ -63,7 +77,7 @@
     args.add("--touch-file")
     args.add(marker)
     args.add("--")
-    args.add(toolchain.rustfmt)
+    args.add(rustfmt_toolchain.rustfmt)
     args.add("--config-path")
     args.add(config)
     args.add("--edition")
@@ -75,7 +89,7 @@
         executable = ctx.executable._process_wrapper,
         inputs = srcs + [config],
         outputs = [marker],
-        tools = [toolchain.rustfmt],
+        tools = [rustfmt_toolchain.all_files],
         arguments = [args],
         mnemonic = "Rustfmt",
     )
@@ -83,21 +97,23 @@
     return marker
 
 def _rustfmt_aspect_impl(target, ctx):
-    srcs = _find_rustfmtable_srcs(target, ctx)
+    crate_info = _get_rustfmt_ready_crate_info(target)
+
+    if not crate_info:
+        return []
+
+    srcs = _find_rustfmtable_srcs(crate_info, ctx)
 
     # If there are no formattable sources, do nothing.
     if not srcs:
         return []
 
-    # Parse the edition to use for formatting from the target
-    edition = target[rust_common.crate_info].edition
+    edition = crate_info.edition
 
-    manifest = _generate_manifest(edition, srcs, ctx)
     marker = _perform_check(edition, srcs, ctx)
 
     return [
         OutputGroupInfo(
-            rustfmt_manifest = depset([manifest]),
             rustfmt_checks = depset([marker]),
         ),
     ]
@@ -109,7 +125,6 @@
 
 Output Groups:
 
-- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings.
 - `rustfmt_checks`: Executes `rustfmt --check` on the specified target.
 
 The build setting `@rules_rust//:rustfmt.toml` is used to control the Rustfmt [configuration settings][cs]
@@ -135,10 +150,52 @@
         ),
     },
     incompatible_use_toolchain_transition = True,
+    required_providers = [
+        [rust_common.crate_info],
+        [rust_common.test_crate_info],
+    ],
     fragments = ["cpp"],
     host_fragments = ["cpp"],
     toolchains = [
-        str(Label("//rust:toolchain_type")),
+        str(Label("//rust/rustfmt:toolchain_type")),
+    ],
+)
+
+def _rustfmt_test_manifest_aspect_impl(target, ctx):
+    crate_info = _get_rustfmt_ready_crate_info(target)
+
+    if not crate_info:
+        return []
+
+    # Parse the edition to use for formatting from the target
+    edition = crate_info.edition
+
+    srcs = _find_rustfmtable_srcs(crate_info, ctx)
+    manifest = _generate_manifest(edition, srcs, ctx)
+
+    return [
+        OutputGroupInfo(
+            rustfmt_manifest = depset([manifest]),
+        ),
+    ]
+
+# This aspect contains functionality split out of `rustfmt_aspect` which broke when
+# `required_providers` was added to it. Aspects which have `required_providers` seems
+# to not function with attributes that also require providers.
+_rustfmt_test_manifest_aspect = aspect(
+    implementation = _rustfmt_test_manifest_aspect_impl,
+    doc = """\
+This aspect is used to gather information about a crate for use in `rustfmt_test`
+
+Output Groups:
+
+- `rustfmt_manifest`: A manifest used by rustfmt binaries to provide crate specific settings.
+""",
+    incompatible_use_toolchain_transition = True,
+    fragments = ["cpp"],
+    host_fragments = ["cpp"],
+    toolchains = [
+        str(Label("//rust/rustfmt:toolchain_type")),
     ],
 )
 
@@ -158,8 +215,13 @@
         is_executable = True,
     )
 
-    manifests = depset(transitive = [target[OutputGroupInfo].rustfmt_manifest for target in ctx.attr.targets])
-    srcs = [depset(_find_rustfmtable_srcs(target)) for target in ctx.attr.targets]
+    crate_infos = [_get_rustfmt_ready_crate_info(target) for target in ctx.attr.targets]
+    srcs = [depset(_find_rustfmtable_srcs(crate_info)) for crate_info in crate_infos if crate_info]
+
+    # Some targets may be included in tests but tagged as "no-format". In this
+    # case, there will be no manifest.
+    manifests = [getattr(target[OutputGroupInfo], "rustfmt_manifest", None) for target in ctx.attr.targets]
+    manifests = depset(transitive = [manifest for manifest in manifests if manifest])
 
     runfiles = ctx.runfiles(
         transitive_files = depset(transitive = srcs + [manifests]),
@@ -192,8 +254,11 @@
     attrs = {
         "targets": attr.label_list(
             doc = "Rust targets to run `rustfmt --check` on.",
-            providers = [rust_common.crate_info],
-            aspects = [rustfmt_aspect],
+            providers = [
+                [rust_common.crate_info],
+                [rust_common.test_crate_info],
+            ],
+            aspects = [_rustfmt_test_manifest_aspect],
         ),
         "_runner": attr.label(
             doc = "The rustfmt test runner",
@@ -205,21 +270,80 @@
     test = True,
 )
 
-def _rustfmt_workspace_name_impl(ctx):
-    output = ctx.actions.declare_file(ctx.label.name)
+def _rustfmt_toolchain_impl(ctx):
+    make_variables = {
+        "RUSTFMT": ctx.file.rustfmt.path,
+    }
 
-    ctx.actions.write(
-        output = output,
-        content = "RUSTFMT_WORKSPACE={}".format(
-            ctx.workspace_name,
-        ),
+    if ctx.attr.rustc:
+        make_variables.update({
+            "RUSTC": ctx.file.rustc.path,
+        })
+
+    make_variable_info = platform_common.TemplateVariableInfo(make_variables)
+
+    all_files = [ctx.file.rustfmt] + ctx.files.rustc_lib
+    if ctx.file.rustc:
+        all_files.append(ctx.file.rustc)
+
+    toolchain = platform_common.ToolchainInfo(
+        rustfmt = ctx.file.rustfmt,
+        rustc = ctx.file.rustc,
+        rustc_lib = depset(ctx.files.rustc_lib),
+        all_files = depset(all_files),
+        make_variables = make_variable_info,
     )
 
-    return [DefaultInfo(
-        files = depset([output]),
-    )]
+    return [
+        toolchain,
+        make_variable_info,
+    ]
 
-rustfmt_workspace_name = rule(
-    implementation = _rustfmt_workspace_name_impl,
-    doc = "A rule for detecting the workspace name for Rustfmt runfiles.",
+rustfmt_toolchain = rule(
+    doc = "A toolchain for [rustfmt](https://rust-lang.github.io/rustfmt/)",
+    implementation = _rustfmt_toolchain_impl,
+    incompatible_use_toolchain_transition = True,
+    attrs = {
+        "rustc": attr.label(
+            doc = "The location of the `rustc` binary. Can be a direct source or a filegroup containing one item.",
+            allow_single_file = True,
+            cfg = "exec",
+        ),
+        "rustc_lib": attr.label(
+            doc = "The libraries used by rustc during compilation.",
+            cfg = "exec",
+        ),
+        "rustfmt": attr.label(
+            doc = "The location of the `rustfmt` binary. Can be a direct source or a filegroup containing one item.",
+            allow_single_file = True,
+            cfg = "exec",
+            mandatory = True,
+        ),
+    },
+    toolchains = [
+        str(Label("@rules_rust//rust:toolchain_type")),
+    ],
+)
+
+def _current_rustfmt_toolchain_impl(ctx):
+    toolchain = ctx.toolchains[str(Label("@rules_rust//rust/rustfmt:toolchain_type"))]
+
+    return [
+        toolchain,
+        toolchain.make_variables,
+        DefaultInfo(
+            files = depset([
+                toolchain.rustfmt,
+            ]),
+            runfiles = ctx.runfiles(transitive_files = toolchain.all_files),
+        ),
+    ]
+
+current_rustfmt_toolchain = rule(
+    doc = "A rule for exposing the current registered `rustfmt_toolchain`.",
+    implementation = _current_rustfmt_toolchain_impl,
+    toolchains = [
+        str(Label("@rules_rust//rust/rustfmt:toolchain_type")),
+    ],
+    incompatible_use_toolchain_transition = True,
 )
diff --git a/rust/private/toolchain_utils.bzl b/rust/private/toolchain_utils.bzl
index 13c94ac..c89a447 100644
--- a/rust/private/toolchain_utils.bzl
+++ b/rust/private/toolchain_utils.bzl
@@ -81,16 +81,20 @@
 def _current_rust_toolchain_impl(ctx):
     toolchain = ctx.toolchains[str(Label("@rules_rust//rust:toolchain_type"))]
 
+    files = [
+        toolchain.rustc,
+        toolchain.rust_doc,
+        toolchain.cargo,
+    ]
+
+    if toolchain.rustfmt:
+        files.append(toolchain.rustfmt)
+
     return [
         toolchain,
         toolchain.make_variables,
         DefaultInfo(
-            files = depset([
-                toolchain.rustc,
-                toolchain.rust_doc,
-                toolchain.rustfmt,
-                toolchain.cargo,
-            ]),
+            files = depset(files),
         ),
     ]
 
diff --git a/rust/private/transitions.bzl b/rust/private/transitions.bzl
index 23336f4..cbc6a06 100644
--- a/rust/private/transitions.bzl
+++ b/rust/private/transitions.bzl
@@ -1,26 +1,7 @@
-# buildifier: disable=module-docstring
+"""Internal transition implementations for core Rust rules"""
+
 load("//rust:defs.bzl", "rust_common")
 
-def _wasm_bindgen_transition(_settings, _attr):
-    """The implementation of the `wasm_bindgen_transition` transition
-
-    Args:
-        _settings (dict): A dict {String:Object} of all settings declared
-            in the inputs parameter to `transition()`
-        _attr (dict): A dict of attributes and values of the rule to which
-            the transition is attached
-
-    Returns:
-        dict: A dict of new build settings values to apply
-    """
-    return {"//command_line_option:platforms": str(Label("//rust/platform:wasm"))}
-
-wasm_bindgen_transition = transition(
-    implementation = _wasm_bindgen_transition,
-    inputs = [],
-    outputs = ["//command_line_option:platforms"],
-)
-
 def _import_macro_dep_bootstrap_transition(_settings, _attr):
     """The implementation of the `import_macro_dep_bootstrap_transition` transition.
 
diff --git a/rust/private/utils.bzl b/rust/private/utils.bzl
index 583dc6e..879e2b6 100644
--- a/rust/private/utils.bzl
+++ b/rust/private/utils.bzl
@@ -15,7 +15,7 @@
 """Utility functions not specific to the rust toolchain."""
 
 load("@bazel_tools//tools/cpp:toolchain_utils.bzl", find_rules_cc_toolchain = "find_cpp_toolchain")
-load(":providers.bzl", "BuildInfo", "CrateInfo", "DepInfo", "DepVariantInfo")
+load(":providers.bzl", "BuildInfo", "CrateGroupInfo", "CrateInfo", "DepInfo", "DepVariantInfo")
 
 UNSUPPORTED_FEATURES = [
     "thin_lto",
@@ -135,7 +135,7 @@
 # so the following doesn't work:
 #     args.add_all(
 #         cc_toolchain.dynamic_runtime_lib(feature_configuration = feature_configuration),
-#         map_each = lambda x: get_lib_name(x, for_windows = toolchain.os.startswith("windows)),
+#         map_each = lambda x: get_lib_name(x, for_windows = toolchain.target_os.startswith("windows)),
 #         format_each = "-ldylib=%s",
 #     )
 def get_lib_name_for_windows(lib):
@@ -223,7 +223,19 @@
             library_to_link.dynamic_library
         )
 
-def _expand_location(ctx, env, data):
+# The normal ctx.expand_location, but with an additional deduplication step.
+# We do this to work around a potential crash, see
+# https://github.com/bazelbuild/bazel/issues/16664
+def dedup_expand_location(ctx, input, targets = []):
+    return ctx.expand_location(input, _deduplicate(targets))
+
+def _deduplicate(xs):
+    return {x: True for x in xs}.keys()
+
+def concat(xss):
+    return [x for xs in xss for x in xs]
+
+def _expand_location_for_build_script_runner(ctx, env, data):
     """A trivial helper for `expand_dict_value_locations` and `expand_list_element_locations`
 
     Args:
@@ -240,7 +252,7 @@
             env = env.replace(directive, "$${pwd}/" + directive)
     return ctx.expand_make_variables(
         env,
-        ctx.expand_location(env, data),
+        dedup_expand_location(ctx, env, data),
         {},
     )
 
@@ -273,7 +285,7 @@
     Returns:
         dict: A dict of environment variables with expanded location macros
     """
-    return dict([(k, _expand_location(ctx, v, data)) for (k, v) in env.items()])
+    return dict([(k, _expand_location_for_build_script_runner(ctx, v, data)) for (k, v) in env.items()])
 
 def expand_list_element_locations(ctx, args, data):
     """Performs location-macro expansion on a list of string values.
@@ -296,7 +308,7 @@
     Returns:
         list: A list of arguments with expanded location macros
     """
-    return [_expand_location(ctx, arg, data) for arg in args]
+    return [_expand_location_for_build_script_runner(ctx, arg, data) for arg in args]
 
 def name_to_crate_name(name):
     """Converts a build target's name into the name of its associated crate.
@@ -463,6 +475,7 @@
         dep_info = dep[DepInfo] if DepInfo in dep else None,
         build_info = dep[BuildInfo] if BuildInfo in dep else None,
         cc_info = dep[CcInfo] if CcInfo in dep else None,
+        crate_group_info = dep[CrateGroupInfo] if CrateGroupInfo in dep else None,
     ) for dep in deps]
 
 def get_import_macro_deps(ctx):
@@ -667,6 +680,48 @@
     # 3) process_wrapper is enabled (this is disabled when compiling process_wrapper itself),
     # 4) the crate_type is rlib or lib.
     return toolchain._pipelined_compilation and \
-           toolchain.os != "windows" and \
+           toolchain.exec_triple.system != "windows" and \
            ctx.attr._process_wrapper and \
            crate_type in ("rlib", "lib")
+
+def crate_root_src(name, srcs, crate_type):
+    """Determines the source file for the crate root, should it not be specified in `attr.crate_root`.
+
+    Args:
+        name (str): The name of the target.
+        srcs (list): A list of all sources for the target Crate.
+        crate_type (str): The type of this crate ("bin", "lib", "rlib", "cdylib", etc).
+
+    Returns:
+        File: The root File object for a given crate. See the following links for more details:
+            - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library
+            - https://doc.rust-lang.org/cargo/reference/cargo-targets.html#binaries
+    """
+    default_crate_root_filename = "main.rs" if crate_type == "bin" else "lib.rs"
+
+    crate_root = (
+        (srcs[0] if len(srcs) == 1 else None) or
+        _shortest_src_with_basename(srcs, default_crate_root_filename) or
+        _shortest_src_with_basename(srcs, name + ".rs")
+    )
+    if not crate_root:
+        file_names = [default_crate_root_filename, name + ".rs"]
+        fail("No {} source file found.".format(" or ".join(file_names)), "srcs")
+    return crate_root
+
+def _shortest_src_with_basename(srcs, basename):
+    """Finds the shortest among the paths in srcs that match the desired basename.
+
+    Args:
+        srcs (list): A list of File objects
+        basename (str): The target basename to match against.
+
+    Returns:
+        File: The File object with the shortest path that matches `basename`
+    """
+    shortest = None
+    for f in srcs:
+        if f.basename == basename:
+            if not shortest or len(f.dirname) < len(shortest.dirname):
+                shortest = f
+    return shortest