From 78e7d26ab958006fa9a0ea127217bc33cff1e21d Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Mon, 10 Aug 2026 17:03:13 +0100 Subject: [PATCH 01/24] typo: Fix previously added typo --- src/compiler/build_context/target_info.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/compiler/build_context/target_info.rs b/src/compiler/build_context/target_info.rs index de4b197a07f..14ce869317b 100644 --- a/src/compiler/build_context/target_info.rs +++ b/src/compiler/build_context/target_info.rs @@ -165,8 +165,6 @@ impl TargetInfo { /// invocation is cached by [`Rustc::cached_output`]. /// /// Search `Tricky` to learn why querying `rustc` several times is needed. - /// - /// When a Workspace is provided, #[tracing::instrument(skip_all)] pub fn new( gctx: &GlobalContext, From 5bfab359a6bc8084aa7edb0f98a25cb79c18d413 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Mon, 10 Aug 2026 15:08:09 +0100 Subject: [PATCH 02/24] test(unit-graph): Capture existing behaviour in unit-graph test --- tests/testsuite/standard_lib.rs | 6 +- tests/testsuite/unit_graph.rs | 242 ++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 1 deletion(-) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index acccdd4699d..137cb22035e 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -17,7 +17,7 @@ struct Setup { real_sysroot: String, } -fn setup() -> Setup { +pub(crate) fn publish_mock_std_registry_packages() { // Our mock sysroot requires a few packages from crates.io, so make sure // they're "published" to crates.io. Also edit their code a bit to make sure // that they have access to our custom crates with custom apis. @@ -83,6 +83,10 @@ fn setup() -> Setup { .add_dep(Dependency::new("rustc-std-workspace-std", "*").optional(true)) .feature("mockbuild", &["rustc-std-workspace-std"]) .publish(); +} + +fn setup() -> Setup { + publish_mock_std_registry_packages(); let p = ProjectBuilder::new(paths::root().join("rustc-wrapper")) .file( diff --git a/tests/testsuite/unit_graph.rs b/tests/testsuite/unit_graph.rs index cead13bc6e8..9d9448a354e 100644 --- a/tests/testsuite/unit_graph.rs +++ b/tests/testsuite/unit_graph.rs @@ -1,5 +1,7 @@ //! Tests for --unit-graph option. +use std::path::Path; + use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry::Package; @@ -237,3 +239,243 @@ fn simple() { ) .run(); } + +#[cargo_test] +fn builtins() { + crate::standard_lib::publish_mock_std_registry_packages(); + let p = project().file("src/lib.rs", "#![no_std]").build(); + let mock_std = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); + + p.cargo("build --unit-graph -Zunstable-options -Zbuild-std=core") + .env("__CARGO_TESTS_ONLY_SRC_ROOT", mock_std) + .masquerade_as_nightly_cargo(&["unit-graph", "build-std"]) + .with_stdout_data( + str![[r#" +{ + "roots": [ + 4 + ], + "units": [ + { + "dependencies": [ + { + "extern_crate_name": "build_script_build", + "index": 2, + "noprelude": false, + "nounused": false, + "public": false + } + ], + "features": [], + "is_std": true, + "mode": "build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/compiler_builtins#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2018", + "kind": [ + "lib" + ], + "name": "compiler_builtins", + "src_path": "[..]/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs", + "test": true + } + }, + { + "dependencies": [], + "features": [], + "is_std": true, + "mode": "build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/compiler_builtins#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 0, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "bin" + ], + "doc": false, + "doctest": false, + "edition": "2018", + "kind": [ + "custom-build" + ], + "name": "build-script-build", + "src_path": "[..]/tests/testsuite/mock-std/library/compiler_builtins/build.rs", + "test": false + } + }, + { + "dependencies": [ + { + "extern_crate_name": "build_script_build", + "index": 1, + "noprelude": false, + "nounused": false, + "public": false + } + ], + "features": [], + "is_std": true, + "mode": "run-custom-build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/compiler_builtins#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": false, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "bin" + ], + "doc": false, + "doctest": false, + "edition": "2018", + "kind": [ + "custom-build" + ], + "name": "build-script-build", + "src_path": "[..]/tests/testsuite/mock-std/library/compiler_builtins/build.rs", + "test": false + } + }, + { + "dependencies": [], + "features": [], + "is_std": true, + "mode": "build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/core#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2018", + "kind": [ + "lib" + ], + "name": "core", + "src_path": "[..]/tests/testsuite/mock-std/library/core/src/lib.rs", + "test": true + } + }, + { + "dependencies": [ + { + "extern_crate_name": "compiler_builtins", + "index": 0, + "noprelude": true, + "nounused": true, + "public": true + }, + { + "extern_crate_name": "core", + "index": 3, + "noprelude": true, + "nounused": true, + "public": true + } + ], + "features": [], + "mode": "build", + "pkg_id": "path+[ROOTURL]/foo#0.0.1", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2015", + "kind": [ + "lib" + ], + "name": "foo", + "src_path": "[ROOT]/foo/src/lib.rs", + "test": true + } + } + ], + "version": 1 +} +"#]] + .is_json(), + ) + .run(); +} From 3b87c7c8ad70436500408b10f21294f039560165 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 21 Jul 2026 16:33:30 +0100 Subject: [PATCH 03/24] test(resolver): Model builtin packages --- crates/resolver-tests/tests/resolve.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 20d32fbf884..ff2e03eb4b0 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -19,6 +19,15 @@ fn test_dependency_with_empty_name() { dep(""); } +#[test] +fn test_builtin_dependency() { + // No way to specify builtin deps or packages to satisfy them + let reg = registry(vec![pkg!("core")]); + let res = resolve(vec![dep("core")], ®).unwrap(); + + assert_same(&res, &names(&["root", "core"])); +} + #[test] fn test_resolving_empty_dependency_list() { let res = resolve(Vec::new(), ®istry(vec![])).unwrap(); From 1bee8648f227841c9115f8bf93138303f22f97f1 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 21 Jul 2026 17:26:24 +0100 Subject: [PATCH 04/24] feat(source): Allow specifying builtin sources --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/cargo-util-schemas/Cargo.toml | 2 +- .../src/core/package_id_spec.rs | 5 +++++ .../src/core/source_kind.rs | 7 +++++++ crates/resolver-tests/src/helpers.rs | 19 +++++++++++++++++++ crates/resolver-tests/tests/resolve.rs | 14 ++++++++------ src/workspace/source_id.rs | 10 ++++++++++ 8 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd8960df300..03821af3d8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,7 +581,7 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.14.3" +version = "0.15.0" dependencies = [ "jiff", "schemars", diff --git a/Cargo.toml b/Cargo.toml index b2fc229f6d9..5c31276f86c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.3.3" } cargo-test-macro = { version = "0.4.14", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.11.4", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.32", path = "crates/cargo-util" } -cargo-util-schemas = { version = "0.14.2", path = "crates/cargo-util-schemas" } +cargo-util-schemas = { version = "0.15.0", path = "crates/cargo-util-schemas" } cargo-util-terminal = { version = "0.1.2", path = "crates/cargo-util-terminal" } cargo_metadata = "0.23.1" clap = "4.6.0" diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 8ed89cc2f77..29c37c76146 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util-schemas" -version = "0.14.3" +version = "0.15.0" rust-version = "1.97" # MSRV:1 edition.workspace = true license.workspace = true diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index c1e636c5193..661ca81b506 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -233,6 +233,11 @@ fn strip_url_protocol(url: &Url) -> Url { impl fmt::Display for PackageIdSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.kind() == Some(&SourceKind::Builtin) { + // Builtins have a very specific pkgid output + write!(f, "builtin://.#{}", self.name)?; + return Ok(()); + } let mut printed_name = false; match self.url { Some(ref url) => { diff --git a/crates/cargo-util-schemas/src/core/source_kind.rs b/crates/cargo-util-schemas/src/core/source_kind.rs index 3794791114d..1c16a251371 100644 --- a/crates/cargo-util-schemas/src/core/source_kind.rs +++ b/crates/cargo-util-schemas/src/core/source_kind.rs @@ -15,6 +15,8 @@ pub enum SourceKind { LocalRegistry, /// A directory-based registry. Directory, + /// Package sources distributed with the rust toolchain + Builtin, } // The hash here is important for what folder packages get downloaded into. @@ -40,6 +42,7 @@ impl SourceKind { SourceKind::SparseRegistry => None, SourceKind::LocalRegistry => Some("local-registry"), SourceKind::Directory => Some("directory"), + SourceKind::Builtin => Some("builtin"), } } } @@ -71,6 +74,10 @@ impl Ord for SourceKind { (_, SourceKind::Directory) => Ordering::Greater, (SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b), + (SourceKind::Git(_), _) => Ordering::Less, + (_, SourceKind::Git(_)) => Ordering::Greater, + + (SourceKind::Builtin, SourceKind::Builtin) => Ordering::Equal, } } } diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index dec0250eb02..c997482bb3c 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -1,5 +1,6 @@ use std::collections::BTreeMap; use std::fmt::Debug; +use std::path::Path; use std::sync::OnceLock; use cargo::util::IntoUrl; @@ -87,6 +88,16 @@ impl, U: AsRef> ToPkgId for (T, U) { } } +pub struct BuiltinPid { + pub name: &'static str, +} + +impl ToPkgId for BuiltinPid { + fn to_pkgid(&self) -> PackageId { + PackageId::try_new(self.name, "0.0.0", builtin_loc()).unwrap() + } +} + #[macro_export] macro_rules! pkg { ($pkgid:expr => [$($deps:expr),* $(,)? ]) => ({ @@ -108,6 +119,14 @@ fn registry_loc() -> SourceId { *example_dot } +fn builtin_loc() -> SourceId { + static LOCAL_PATH: OnceLock = OnceLock::new(); + let local_path = LOCAL_PATH.get_or_init(|| { + SourceId::for_builtin(Path::new(&std::env::current_dir().unwrap())).unwrap() + }); + *local_path +} + pub fn pkg(name: T) -> Summary { pkg_dep(name, Vec::new()) } diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index ff2e03eb4b0..46a83327824 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -6,8 +6,8 @@ use snapbox::str; use resolver_tests::{ helpers::{ - ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names, - names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry, + BuiltinPid, ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, + loc_names, names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry, }, pkg, resolve, resolve_with_global_context, }; @@ -21,11 +21,13 @@ fn test_dependency_with_empty_name() { #[test] fn test_builtin_dependency() { - // No way to specify builtin deps or packages to satisfy them - let reg = registry(vec![pkg!("core")]); - let res = resolve(vec![dep("core")], ®).unwrap(); + let core = BuiltinPid { name: "core" }; + let reg = registry(vec![pkg!(core)]); - assert_same(&res, &names(&["root", "core"])); + // No way to specify builtin deps - fails + //let res = resolve(vec![dep("core")], ®).unwrap(); + + //assert_same(&res, &names(&["root", "core"])); } #[test] diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index dbfbc367dae..a2b13427c87 100644 --- a/src/workspace/source_id.rs +++ b/src/workspace/source_id.rs @@ -204,6 +204,14 @@ impl SourceId { SourceId::new(SourceKind::Path, url, None) } + /// Creates a `SourceId` from a filesystem path representing a builtin package. + /// + /// `path`: an absolute path. + pub fn for_builtin(path: &Path) -> CargoResult { + let url = path.into_url()?; + SourceId::new(SourceKind::Builtin, url, None) + } + /// Creates a `SourceId` from a filesystem path. /// /// `path`: an absolute path. @@ -403,6 +411,7 @@ impl SourceId { } Ok(Box::new(PathSource::new(&path, self, gctx))) } + SourceKind::Builtin => todo!("builtin source"), SourceKind::Registry | SourceKind::SparseRegistry => { Ok(Box::new(RegistrySource::remote(self, gctx)?)) } @@ -663,6 +672,7 @@ impl fmt::Display for SourceId { Ok(()) } SourceKind::Path => write!(f, "{}", url_display(&self.inner.url)), + SourceKind::Builtin => write!(f, "builtin {}", url_display(&self.inner.url)), SourceKind::Registry | SourceKind::SparseRegistry => { write!(f, "registry `{}`", self.display_registry_name()) } From e7473c13144978977af4db4794d920ab99c01941 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 22 Jul 2026 16:24:00 +0100 Subject: [PATCH 05/24] feat(resolver): Allow creating builtin dependencies --- crates/resolver-tests/src/helpers.rs | 11 +++++++++++ crates/resolver-tests/tests/resolve.rs | 8 ++++---- src/resolver/encode.rs | 2 +- src/workspace/source_id.rs | 5 +++++ 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index c997482bb3c..24efc3efacc 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -88,6 +88,7 @@ impl, U: AsRef> ToPkgId for (T, U) { } } +#[derive(Copy, Clone)] pub struct BuiltinPid { pub name: &'static str, } @@ -234,6 +235,10 @@ pub fn dep_loc(name: &str, location: &str) -> Dependency { Dependency::parse(name, Some("1.0.0"), source_id).unwrap() } +pub fn dep_builtin(name: &str) -> Dependency { + Dependency::parse(name, None, builtin_loc()).unwrap() +} + pub fn dep_kind(name: &str, kind: DepKind) -> Dependency { let mut dep = dep(name); dep.set_kind(kind); @@ -254,6 +259,12 @@ pub fn names(names: &[P]) -> Vec { names.iter().map(|name| name.to_pkgid()).collect() } +/// For a set of name specifiers of varying types +#[macro_export] +macro_rules! names { + ($($name:expr),* $(,)?) => {&vec![$($name.to_pkgid()),*]}; +} + pub fn loc_names(names: &[(&'static str, &'static str)]) -> Vec { names .iter() diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 46a83327824..31e9a904539 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1,6 +1,7 @@ use cargo::util::GlobalContext; use cargo::workspace::Dependency; use cargo::workspace::dependency::DepKind; +use resolver_tests::helpers::dep_builtin; use snapbox::assert_data_eq; use snapbox::str; @@ -9,7 +10,7 @@ use resolver_tests::{ BuiltinPid, ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names, names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry, }, - pkg, resolve, resolve_with_global_context, + names, pkg, resolve, resolve_with_global_context, }; #[test] @@ -24,10 +25,9 @@ fn test_builtin_dependency() { let core = BuiltinPid { name: "core" }; let reg = registry(vec![pkg!(core)]); - // No way to specify builtin deps - fails - //let res = resolve(vec![dep("core")], ®).unwrap(); + let res = resolve(vec![dep_builtin("core")], ®).unwrap(); - //assert_same(&res, &names(&["root", "core"])); + assert_same(&res, &names!("root", core)); } #[test] diff --git a/src/resolver/encode.rs b/src/resolver/encode.rs index 3bc4d0921af..6ef1752f265 100644 --- a/src/resolver/encode.rs +++ b/src/resolver/encode.rs @@ -661,7 +661,7 @@ pub fn encodable_package_id( } fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option { - if id.is_path() { + if id.is_path() || id.is_builtin() { None } else { Some( diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index a2b13427c87..6c8d043e969 100644 --- a/src/workspace/source_id.rs +++ b/src/workspace/source_id.rs @@ -353,6 +353,11 @@ impl SourceId { self.inner.kind == SourceKind::Path } + /// Returns `true` if this source is built into Cargo + pub fn is_builtin(self) -> bool { + self.inner.kind == SourceKind::Builtin + } + /// Returns the local path if this is a path dependency. pub fn local_path(self) -> Option { if self.inner.kind != SourceKind::Path { From c78fd632a5e4a10a713c050689497f8d3ad9f0e0 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 17:04:12 +0100 Subject: [PATCH 06/24] test(resolver): Add negative resolver tests --- crates/resolver-tests/tests/resolve.rs | 33 ++++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 31e9a904539..739a5efe176 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -20,16 +20,6 @@ fn test_dependency_with_empty_name() { dep(""); } -#[test] -fn test_builtin_dependency() { - let core = BuiltinPid { name: "core" }; - let reg = registry(vec![pkg!(core)]); - - let res = resolve(vec![dep_builtin("core")], ®).unwrap(); - - assert_same(&res, &names!("root", core)); -} - #[test] fn test_resolving_empty_dependency_list() { let res = resolve(Vec::new(), ®istry(vec![])).unwrap(); @@ -1047,3 +1037,26 @@ failed to select a version for `F` which could resolve this conflict "#]] ); } + +#[test] +fn test_builtin_dependency() { + let core = BuiltinPid { name: "core" }; + let reg = registry(vec![pkg!(core)]); + + let res = resolve(vec![dep_builtin("core")], ®).unwrap(); + + assert_same(&res, &names!("root", core)); +} + +#[test] +fn normal_dependency_is_not_satisfied_by_builtin_package() { + let core = BuiltinPid { name: "core" }; + let reg = registry(vec![pkg!(core)]); + + assert!(resolve(vec![dep("core")], ®).is_err()); +} + +#[test] +fn missing_builtin_dependency_errors() { + assert!(resolve(vec![dep_builtin("core")], ®istry(vec![])).is_err()); +} From c7483da695cb3300583de98ba666c573a95eac2a Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 28 Jul 2026 16:55:09 +0100 Subject: [PATCH 07/24] feat(sources): generate opaque summaries to satisfy builtin dependencies This commit is currently an unused API and will be tested in later commits --- src/sources/builtin.rs | 116 ++++++++++++++++++++++++++++++++++++ src/sources/mod.rs | 2 + src/sources/path.rs | 5 +- src/workspace/dependency.rs | 9 +++ src/workspace/source_id.rs | 13 +++- src/workspace/summary.rs | 25 ++++++++ 6 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 src/sources/builtin.rs diff --git a/src/sources/builtin.rs b/src/sources/builtin.rs new file mode 100644 index 00000000000..be20903cf7d --- /dev/null +++ b/src/sources/builtin.rs @@ -0,0 +1,116 @@ +use std::{cell::RefCell, path::Path}; + +use crate::{ + CargoResult, GlobalContext, + sources::{ + IndexSummary, RecursivePathSource, + source::{MaybePackage, QueryKind, Source}, + }, + util::data_structures::HashMap, + workspace::{Dependency, Package, PackageId, SourceId, Summary}, +}; + +/// A builtin source represents standard library packages used in build-std, which are "built into" +/// the toolchain. Returns opaque `Summary`s - see [`Summary::new_opaque()`] +/// +/// It wraps a [`RecursivePathSource`] and uses that to discover packages +pub struct BuiltinSource<'gctx> { + /// The unique identifier for this source + source_id: SourceId, + /// The underlying path source which discovers packages + path_source: RecursivePathSource<'gctx>, + /// Opaque summaries cached by the real package ID returned by the path source. + opaque_summaries: RefCell>, +} + +impl<'gctx> BuiltinSource<'gctx> { + pub fn new(path: &Path, source_id: SourceId, gctx: &'gctx GlobalContext) -> Self { + assert!( + source_id.is_builtin(), + "source `{source_id} is not a builtin" + ); + let path = path + .join("lib") + .join("rustlib") + .join("src") + .join("rust") + .join("library"); + let path_source = RecursivePathSource::new(&path, source_id, gctx); + Self { + source_id, + path_source, + opaque_summaries: RefCell::new(HashMap::default()), + } + } +} + +#[async_trait::async_trait(?Send)] +impl<'gctx> Source for BuiltinSource<'gctx> { + /// All builtin dependencies are opaque, so this will return a summary without any dependencies when queried + async fn query( + &self, + dep: &Dependency, + kind: QueryKind, + f: &mut dyn FnMut(IndexSummary), + ) -> CargoResult<()> { + if !dep.is_opaque() { + // Avoid loading packages in the path source if it's not needed + return Ok(()); + } + self.path_source + .query(dep, kind, &mut |summary| { + let summary = match summary { + IndexSummary::Candidate(summary) => { + let package_id = summary.package_id(); + let opaque = self + .opaque_summaries + .borrow_mut() + .entry(package_id) + .or_insert_with(|| Summary::new_opaque(package_id, self.source_id)) + .clone(); + IndexSummary::Candidate(opaque) + } + summary => summary, + }; + f(summary); + }) + .await + } + + fn supports_checksums(&self) -> bool { + self.path_source.supports_checksums() + } + + fn requires_precise(&self) -> bool { + self.path_source.requires_precise() + } + + fn source_id(&self) -> SourceId { + self.source_id + } + + async fn download(&self, id: PackageId) -> CargoResult { + self.path_source.download(id).await + } + + async fn finish_download(&self, id: PackageId, data: Vec) -> CargoResult { + self.path_source.finish_download(id, data).await + } + + fn fingerprint(&self, pkg: &Package) -> CargoResult { + self.path_source.fingerprint(pkg) + } + + fn describe(&self) -> String { + self.source_id.to_string() + } + + fn invalidate_cache(&self) { + // The RecursivePathSource does not clear its cached Packages, meaning nothing can + // invalidate our cached summaries + } + + fn set_quiet(&mut self, quiet: bool) { + self.path_source.set_quiet(quiet); + } +} diff --git a/src/sources/mod.rs b/src/sources/mod.rs index 5d92c92770b..999d4cf2b58 100644 --- a/src/sources/mod.rs +++ b/src/sources/mod.rs @@ -26,6 +26,7 @@ //! //! [source replacement]: https://doc.rust-lang.org/nightly/cargo/reference/source-replacement.html +pub use self::builtin::BuiltinSource; pub use self::config::SourceConfigMap; pub use self::directory::DirectorySource; pub use self::git::GitSource; @@ -37,6 +38,7 @@ pub use self::registry::{ }; pub use self::replaced::ReplacedSource; +pub mod builtin; pub mod config; pub mod directory; pub mod git; diff --git a/src/sources/path.rs b/src/sources/path.rs index 1d235237bc3..ac376106e73 100644 --- a/src/sources/path.rs +++ b/src/sources/path.rs @@ -1184,12 +1184,15 @@ fn read_nested_packages( // Registry sources are not allowed to have `path=` dependencies because // they're all translated to actual registry dependencies. // + // The standard library source intentionally does not include some test crates leading to broken + // dev-dependencies links. The main directory walk already gets all the packages we need. + // // We normalize the path here ensure that we don't infinitely walk around // looking for crates. By normalizing we ensure that we visit this crate at // most once. // // TODO: filesystem/symlink implications? - if !source_id.is_registry() { + if !source_id.is_registry() && !source_id.is_builtin() { for p in nested.iter() { let path = paths::normalize_path(&path.join(p)); let result = diff --git a/src/workspace/dependency.rs b/src/workspace/dependency.rs index b4f4b9c8095..9103c956e71 100644 --- a/src/workspace/dependency.rs +++ b/src/workspace/dependency.rs @@ -51,6 +51,10 @@ struct Inner { // This dependency should be used only for this platform. // `None` means *all platforms*. platform: Option, + // Opaque dependencies should not be traversed any deeper by the resolver. Required packages should + // be resolved as roots of a separate resolver run and the dependency handled during unit + // generation. + opaque: bool, } #[derive(Serialize)] @@ -162,6 +166,7 @@ impl Dependency { platform: None, explicit_name_in_toml: None, artifact: None, + opaque: source_id.is_builtin(), // All deps on builtin packages are opaque }), } } @@ -412,6 +417,10 @@ impl Dependency { self.inner.optional } + pub fn is_opaque(&self) -> bool { + self.inner.opaque + } + /// Returns `true` if the default features of the dependency are requested. pub fn uses_default_features(&self) -> bool { self.inner.default_features diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index 6c8d043e969..5811c38fee1 100644 --- a/src/workspace/source_id.rs +++ b/src/workspace/source_id.rs @@ -1,7 +1,9 @@ use crate::context; use crate::sources::registry::CRATES_IO_HTTP_INDEX; use crate::sources::source::Source; -use crate::sources::{CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY, DirectorySource}; +use crate::sources::{ + BuiltinSource, CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY, DirectorySource, +}; use crate::sources::{GitSource, PathSource, RegistrySource}; use crate::util::data_structures::HashSet; use crate::util::interning::InternedString; @@ -416,7 +418,14 @@ impl SourceId { } Ok(Box::new(PathSource::new(&path, self, gctx))) } - SourceKind::Builtin => todo!("builtin source"), + SourceKind::Builtin => { + let path = self + .inner + .url + .to_file_path() + .expect("builtin sources cannot be remote"); + Ok(Box::new(BuiltinSource::new(&path, self, gctx))) + } SourceKind::Registry | SourceKind::SparseRegistry => { Ok(Box::new(RegistrySource::remote(self, gctx)?)) } diff --git a/src/workspace/summary.rs b/src/workspace/summary.rs index 1cf96a88d99..29eed621154 100644 --- a/src/workspace/summary.rs +++ b/src/workspace/summary.rs @@ -97,6 +97,31 @@ impl Summary { }) } + /// Creates a dummy Summary to satisfy an opaque dependency + /// + /// The summary has no dependencies and is artificial - it is used purely guide the resolver + /// by satisfying opaque dependencies and is discarded during Unit generation. The real + /// packages that are converted into `Unit`s come from a different invocation of the resolver. + pub fn new_opaque(pkg_id: PackageId, sid: SourceId) -> Self { + // Currently only builtin packages can be opaque + assert!(sid.is_builtin()); + Summary { + inner: Arc::new(Inner { + package_id: pkg_id, + // opaque dependencies - the real deps are inserted during unit generation + dependencies: vec![], + // Features are currently ignored during unit generation. May need to be changed + // when implementing feature specification for explicit builtin dependencies + features: Arc::new(BTreeMap::new()), + checksum: None, + links: None, + // Builtins are always valid for our current toolchain + rust_version: None, + pubtime: None, + }), + } + } + pub fn package_id(&self) -> PackageId { self.inner.package_id } From 038ad3a182b96425716eec244a8522046a1a0cf5 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 4 Aug 2026 18:10:04 +0100 Subject: [PATCH 08/24] feat(resolver): Add directive to enable injecting builtins This commit is currently an unused API and will be tested in later commits --- src/ops/resolve.rs | 2 ++ src/resolver/mod.rs | 12 +++++++++++- src/resolver/types.rs | 24 +++++++++++++++++++----- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/ops/resolve.rs b/src/ops/resolve.rs index aa5821210dd..58f9086ca16 100644 --- a/src/ops/resolve.rs +++ b/src/ops/resolve.rs @@ -520,6 +520,8 @@ pub fn resolve_with_previous<'gctx>( ResolveOpts { dev_deps, features: RequestedFeatures::CliFeatures(features), + // TODO: set this to true when build-std is enabled + inject_builtins: false, }, ) }) diff --git a/src/resolver/mod.rs b/src/resolver/mod.rs index ce1ca941efc..15f2dfa9b05 100644 --- a/src/resolver/mod.rs +++ b/src/resolver/mod.rs @@ -59,6 +59,7 @@ //! over the place. use crate::util::data_structures::{HashMap, HashSet}; +use crate::workspace::dependency::DepKind; use rustc_hash::FxBuildHasher; use std::collections::BTreeMap; use std::rc::Rc; @@ -240,7 +241,7 @@ fn activate_deps_loop( while let Some((just_here_for_the_error_messages, frame)) = remaining_deps.pop_most_constrained() { - let (mut parent, (mut dep, candidates, mut features)) = frame; + let (mut parent, parent_inject_builtins, (mut dep, candidates, mut features)) = frame; // If we spend a lot of time here (we shouldn't in most cases) then give // a bit of a visual indicator as to what we're doing. @@ -393,12 +394,20 @@ fn activate_deps_loop( }; let pid = candidate.package_id(); + // The deps frame inject_builtins field is inherited from the parent, and is a baseline + // for all siblings. We override that baseline to false for a dep that's already builtin + // or for the host + let inject_builtins = parent_inject_builtins + && !dep.source_id().is_builtin() + && dep.kind() != DepKind::Build; + let opts = ResolveOpts { dev_deps: false, features: RequestedFeatures::DepFeatures { features: Rc::clone(&features), uses_default_features: dep.uses_default_features(), }, + inject_builtins, }; trace!( "{}[{}]>{} trying {}", @@ -689,6 +698,7 @@ fn activate( let frame = DepsFrame { parent: candidate, just_for_error_messages: false, + inject_builtins: opts.inject_builtins, remaining_siblings: RcVecIter::new(Rc::clone(deps)), }; Ok(Some((frame, now.elapsed()))) diff --git a/src/resolver/types.rs b/src/resolver/types.rs index ebd864cc29b..056e152cb37 100644 --- a/src/resolver/types.rs +++ b/src/resolver/types.rs @@ -136,7 +136,7 @@ impl ResolveBehavior { } } -/// Options for how the resolve should work. +/// Options for how a Summary should be activated during the resolve #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ResolveOpts { /// Whether or not dev-dependencies should be included. @@ -146,6 +146,10 @@ pub struct ResolveOpts { pub dev_deps: bool, /// Set of features requested on the command-line. pub features: RequestedFeatures, + /// Whether or not to inject builtin dependencies. Host deps like proc_macros and build scripts + /// should not use build-std, so therefore build-dependencies and transitive + /// dependencies via build-dependencies do not have builtin dependencies. + pub inject_builtins: bool, } impl ResolveOpts { @@ -154,11 +158,16 @@ impl ResolveOpts { ResolveOpts { dev_deps: true, features: RequestedFeatures::CliFeatures(CliFeatures::new_all(true)), + inject_builtins: false, } } - pub fn new(dev_deps: bool, features: RequestedFeatures) -> ResolveOpts { - ResolveOpts { dev_deps, features } + pub fn new(dev_deps: bool, features: RequestedFeatures, inject_builtins: bool) -> ResolveOpts { + ResolveOpts { + dev_deps, + features, + inject_builtins, + } } } @@ -218,6 +227,7 @@ impl PackageId { pub struct DepsFrame { pub parent: Summary, pub just_for_error_messages: bool, + pub inject_builtins: bool, pub remaining_siblings: RcVecIter, } @@ -294,7 +304,7 @@ impl RemainingDeps { self.data.insert((x, insertion_time)); self.time += 1; } - pub fn pop_most_constrained(&mut self) -> Option<(bool, (Summary, DepInfo))> { + pub fn pop_most_constrained(&mut self) -> Option<(bool, (Summary, bool, DepInfo))> { while let Some((mut deps_frame, insertion_time)) = self.data.remove_min() { let just_here_for_the_error_messages = deps_frame.just_for_error_messages; @@ -304,8 +314,12 @@ impl RemainingDeps { let sibling = deps_frame.remaining_siblings.iter().next().cloned(); if let Some(sibling) = sibling { let parent = Summary::clone(&deps_frame.parent); + let inject_builtins = deps_frame.inject_builtins; self.data.insert((deps_frame, insertion_time)); - return Some((just_here_for_the_error_messages, (parent, sibling))); + return Some(( + just_here_for_the_error_messages, + (parent, inject_builtins, sibling), + )); } } None From 703e41de535b7c88a9907fa526db4cd4715cc3e1 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 17:35:03 +0100 Subject: [PATCH 09/24] test(resolver): Test for implicit builtin deps --- crates/resolver-tests/src/helpers.rs | 50 ++++++++++++++++++++++++++ crates/resolver-tests/src/lib.rs | 25 ++++++++++--- crates/resolver-tests/tests/resolve.rs | 31 ++++++++++++++-- 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index 24efc3efacc..9c5db445451 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -3,7 +3,9 @@ use std::fmt::Debug; use std::path::Path; use std::sync::OnceLock; +use cargo::GlobalContext; use cargo::util::IntoUrl; +use cargo::util::data_structures::HashMap; use cargo::workspace::dependency::DepKind; use cargo::workspace::{Dependency, GitReference, PackageId, SourceId, Summary}; @@ -99,6 +101,18 @@ impl ToPkgId for BuiltinPid { } } +#[derive(Copy, Clone)] +pub struct BuiltinPidWithGctx<'a> { + pub name: &'static str, + pub gctx: &'a GlobalContext, +} + +impl<'a> ToPkgId for BuiltinPidWithGctx<'a> { + fn to_pkgid(&self) -> PackageId { + PackageId::try_new(self.name, "0.0.0", builtin_loc_sysroot(self.gctx)).unwrap() + } +} + #[macro_export] macro_rules! pkg { ($pkgid:expr => [$($deps:expr),* $(,)? ]) => ({ @@ -128,6 +142,42 @@ fn builtin_loc() -> SourceId { *local_path } +fn builtin_loc_sysroot(gctx: &GlobalContext) -> SourceId { + static LOCAL_PATH: OnceLock = OnceLock::new(); + let local_path = LOCAL_PATH.get_or_init(|| { + SourceId::for_builtin( + gctx.get_sysroot(&gctx.load_global_rustc(None).unwrap()) + .unwrap(), + ) + .unwrap() + }); + *local_path +} + +pub fn gctx_for_build_std() -> GlobalContext { + let mut gctx = GlobalContext::default().unwrap(); + gctx.nightly_features_allowed = true; + gctx.configure( + 1, + false, + None, + false, + false, + false, + &None, + &["build-std=core".to_owned()], + &[], + ) + .unwrap(); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/testsuite/mock-std/library"); + let env = HashMap::from_iter([( + "__CARGO_TESTS_ONLY_SRC_ROOT".to_owned(), + root.into_os_string().into_string().unwrap(), + )]); + gctx.set_env(env); + gctx +} + pub fn pkg(name: T) -> Summary { pkg_dep(name, Vec::new()) } diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index cad66c896d2..85a4b80e319 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -57,11 +57,12 @@ pub fn resolve_and_validated_raw( root_pkg_id: PackageId, sat_resolver: &mut SatResolver, ) -> CargoResult)>> { - let resolve = resolve_with_global_context_raw( + let resolve = resolve_with_raw( deps.clone(), registry, root_pkg_id, &GlobalContext::default().unwrap(), + ResolveOpts::everything(), ); match resolve { @@ -120,15 +121,31 @@ pub fn resolve_with_global_context( registry: &[Summary], gctx: &GlobalContext, ) -> CargoResult)>> { - let resolve = resolve_with_global_context_raw(deps, registry, pkg_id("root"), gctx)?; + let resolve = resolve_with_raw( + deps, + registry, + pkg_id("root"), + gctx, + ResolveOpts::everything(), + )?; Ok(collect_features(&resolve)) } -pub fn resolve_with_global_context_raw( +pub fn resolve_with_gctx_opts( + deps: Vec, + registry: &[Summary], + gctx: &GlobalContext, + opts: ResolveOpts, +) -> CargoResult { + resolve_with_raw(deps, registry, pkg_id("root"), gctx, opts) +} + +pub fn resolve_with_raw( deps: Vec, registry: &[Summary], root_pkg_id: PackageId, gctx: &GlobalContext, + opts: ResolveOpts, ) -> CargoResult { struct MyRegistry<'a> { list: &'a [Summary], @@ -190,8 +207,6 @@ pub fn resolve_with_global_context_raw( let root_summary = Summary::new(root_pkg_id, deps, &BTreeMap::new(), None::<&String>, None).unwrap(); - let opts = ResolveOpts::everything(); - let start = Instant::now(); let mut version_prefs = VersionPreferences::default(); if gctx.cli_unstable().minimal_versions { diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 739a5efe176..dc2311c77b6 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1,7 +1,9 @@ +use cargo::resolver::ResolveOpts; use cargo::util::GlobalContext; use cargo::workspace::Dependency; use cargo::workspace::dependency::DepKind; -use resolver_tests::helpers::dep_builtin; +use resolver_tests::helpers::gctx_for_build_std; +use resolver_tests::helpers::{BuiltinPidWithGctx, dep_builtin}; use snapbox::assert_data_eq; use snapbox::str; @@ -10,7 +12,7 @@ use resolver_tests::{ BuiltinPid, ToDep, ToPkgId, assert_contains, assert_same, dep, dep_kind, dep_loc, dep_req, loc_names, names, pkg, pkg_dep, pkg_dep_with, pkg_id, pkg_loc, registry, }, - names, pkg, resolve, resolve_with_global_context, + names, pkg, resolve, resolve_with_gctx_opts, resolve_with_global_context, }; #[test] @@ -1060,3 +1062,28 @@ fn normal_dependency_is_not_satisfied_by_builtin_package() { fn missing_builtin_dependency_errors() { assert!(resolve(vec![dep_builtin("core")], ®istry(vec![])).is_err()); } + +#[test] +fn injects_builtins_when_required() { + let gctx = gctx_for_build_std(); + + let core = BuiltinPidWithGctx { + name: "core", + gctx: &gctx, + }; + let compiler_builtins = BuiltinPidWithGctx { + name: "compiler_builtins", + gctx: &gctx, + }; + let reg = registry(vec![pkg!(core), pkg!(compiler_builtins)]); + + let mut opts = ResolveOpts::everything(); + opts.inject_builtins = true; + let resolve = resolve_with_gctx_opts(Vec::new(), ®, &gctx, opts).unwrap(); + + let root_deps = resolve + .deps(pkg_id("root")) + .map(|(pkg_id, _)| pkg_id) + .collect::>(); + assert_same(&root_deps, &[]); +} From 984988be04f15d277632590e7798a7bae89a211b Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 5 Aug 2026 16:06:48 +0100 Subject: [PATCH 10/24] feat(resolver): Inject builtin dependencies --- crates/resolver-tests/src/helpers.rs | 7 ++----- crates/resolver-tests/tests/resolve.rs | 2 +- src/compiler/standard_lib.rs | 24 +++++++++++++--------- src/resolver/dep_cache.rs | 12 ++++++++++- src/resolver/mod.rs | 28 +++++++++++++++++++++++++- src/sources/builtin.rs | 8 +------- src/workspace/dependency.rs | 24 ++++++++++++++++++++++ 7 files changed, 81 insertions(+), 24 deletions(-) diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index 9c5db445451..fa9e1356027 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -4,6 +4,7 @@ use std::path::Path; use std::sync::OnceLock; use cargo::GlobalContext; +use cargo::compiler::standard_lib::detect_sysroot_src_path; use cargo::util::IntoUrl; use cargo::util::data_structures::HashMap; use cargo::workspace::dependency::DepKind; @@ -145,11 +146,7 @@ fn builtin_loc() -> SourceId { fn builtin_loc_sysroot(gctx: &GlobalContext) -> SourceId { static LOCAL_PATH: OnceLock = OnceLock::new(); let local_path = LOCAL_PATH.get_or_init(|| { - SourceId::for_builtin( - gctx.get_sysroot(&gctx.load_global_rustc(None).unwrap()) - .unwrap(), - ) - .unwrap() + SourceId::for_builtin(&detect_sysroot_src_path(gctx, None).unwrap()).unwrap() }); *local_path } diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index dc2311c77b6..c28f8727ecd 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1085,5 +1085,5 @@ fn injects_builtins_when_required() { .deps(pkg_id("root")) .map(|(pkg_id, _)| pkg_id) .collect::>(); - assert_same(&root_deps, &[]); + assert_same(&root_deps, names!(core, compiler_builtins)) } diff --git a/src/compiler/standard_lib.rs b/src/compiler/standard_lib.rs index 3af824e2cb0..22b51d1464b 100644 --- a/src/compiler/standard_lib.rs +++ b/src/compiler/standard_lib.rs @@ -7,7 +7,7 @@ use crate::ops::{self, Packages}; use crate::resolver::HasDevUnits; use crate::resolver::Resolve; use crate::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; -use crate::util::errors::CargoResult; +use crate::util::{CargoResult, GlobalContext}; use crate::workspace::profiles::{Profiles, UnitFor}; use crate::workspace::{PackageId, PackageSet, Workspace}; @@ -16,7 +16,11 @@ use std::path::PathBuf; use super::BuildConfig; -fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -> HashSet<&'a str> { +pub fn std_crates<'a>( + crates: &'a [String], + default: &'static str, + units: &[Unit], +) -> HashSet<&'a str> { let mut crates = HashSet::from_iter(crates.iter().map(|s| s.as_str())); // This is a temporary hack until there is a more principled way to // declare dependencies in Cargo.toml. @@ -54,7 +58,7 @@ pub fn resolve_std<'gctx>( crates: &[String], kinds: &[CompileKind], ) -> CargoResult<(PackageSet<'gctx>, Resolve, ResolvedFeatures)> { - let src_path = detect_sysroot_src_path(ws)?; + let src_path = detect_sysroot_src_path(ws.gctx(), Some(ws))?; let std_ws_manifest_path = src_path.join("Cargo.toml"); let gctx = ws.gctx(); // TODO: Consider doing something to enforce --locked? Or to prevent the @@ -217,15 +221,17 @@ fn generate_roots( Ok(()) } -fn detect_sysroot_src_path(ws: &Workspace<'_>) -> CargoResult { - if let Some(s) = ws.gctx().get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") { +pub fn detect_sysroot_src_path( + gctx: &GlobalContext, + ws: Option<&Workspace<'_>>, +) -> CargoResult { + if let Some(s) = gctx.get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") { return Ok(s.into()); } // NOTE: This is temporary until we figure out how to acquire the source. - let rustc = ws.gctx().load_global_rustc(Some(ws))?; - let src_path = ws - .gctx() + let rustc = gctx.load_global_rustc(ws)?; + let src_path = gctx .get_sysroot(&rustc) .expect("able to invoke rustc") .join("lib") @@ -240,7 +246,7 @@ fn detect_sysroot_src_path(ws: &Workspace<'_>) -> CargoResult { library, try:\n rustup component add rust-src", lock ); - match ws.gctx().get_env("RUSTUP_TOOLCHAIN") { + match gctx.get_env("RUSTUP_TOOLCHAIN") { Ok(rustup_toolchain) => { anyhow::bail!("{} --toolchain {}", msg, rustup_toolchain); } diff --git a/src/resolver/dep_cache.rs b/src/resolver/dep_cache.rs index f2a928fe231..dfa94246e07 100644 --- a/src/resolver/dep_cache.rs +++ b/src/resolver/dep_cache.rs @@ -219,6 +219,8 @@ pub struct RegistryQueryer<'a, T: Registry> { (Option, Summary, ResolveOpts), (Rc<(HashSet, Rc>)>, bool), >, + /// The set of builtin dependencies to inject when appropriate + implicit_builtin_deps: &'a [Dependency], } impl<'a, T: Registry> RegistryQueryer<'a, T> { @@ -226,6 +228,7 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { registry: &'a T, replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, + implicit_builtin_deps: &'a [Dependency], ) -> Self { let inner = Rc::new(RegistryQueryerAsync::new( registry, @@ -236,6 +239,7 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { inner: inner.clone(), poller: LocalPollAdapter::new(inner), summary_cache: HashMap::default(), + implicit_builtin_deps, } } @@ -308,7 +312,13 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { // First, figure out our set of dependencies based on the requested set // of features. This also calculates what features we're going to enable // for our own dependencies. - let (used_features, deps) = resolve_features(parent, candidate, opts)?; + let (used_features, mut deps) = resolve_features(parent, candidate, opts)?; + + if opts.inject_builtins { + for dep in self.implicit_builtin_deps { + deps.push((dep.clone(), Rc::new(BTreeSet::default()))); + } + } // Next, transform all dependencies into a list of possible candidates // which can satisfy that dependency. diff --git a/src/resolver/mod.rs b/src/resolver/mod.rs index 15f2dfa9b05..861a5423291 100644 --- a/src/resolver/mod.rs +++ b/src/resolver/mod.rs @@ -58,7 +58,9 @@ //! that we're implementing something that probably shouldn't be allocating all //! over the place. +use crate::compiler::standard_lib::{detect_sysroot_src_path, std_crates}; use crate::util::data_structures::{HashMap, HashSet}; +use crate::util::interning::InternedString; use crate::workspace::dependency::DepKind; use rustc_hash::FxBuildHasher; use std::collections::BTreeMap; @@ -135,7 +137,31 @@ pub fn resolve( .cli_unstable() .direct_minimal_versions .then_some(VersionOrdering::MinimumVersionsFirst); - let mut registry = RegistryQueryer::new(registry, replacements, version_prefs); + + let implicit_builtin_deps: Vec = if summaries + .iter() + .any(|(_, opts)| opts.inject_builtins) + { + let crates = gctx.cli_unstable().build_std.as_deref().unwrap_or_default(); + // We default to "std" here as we don't yet know the default crates for any of the targets + // we're building for. Unit generation will discard any builtin Summaries that are not + // required for each target + let crates = std_crates(crates, "std", &[]); + let sysroot_src = detect_sysroot_src_path(gctx, None)?; + crates + .iter() + .map(|&name| Dependency::new_implicit_builtin(InternedString::from(name), &sysroot_src)) + .collect::>>()? + } else { + vec![] + }; + + let mut registry = RegistryQueryer::new( + registry, + replacements, + version_prefs, + &implicit_builtin_deps, + ); // Global cache of the reasons for each time we backtrack. let mut past_conflicting_activations = conflict_cache::ConflictCache::new(); diff --git a/src/sources/builtin.rs b/src/sources/builtin.rs index be20903cf7d..023b72ebbc6 100644 --- a/src/sources/builtin.rs +++ b/src/sources/builtin.rs @@ -29,13 +29,7 @@ impl<'gctx> BuiltinSource<'gctx> { source_id.is_builtin(), "source `{source_id} is not a builtin" ); - let path = path - .join("lib") - .join("rustlib") - .join("src") - .join("rust") - .join("library"); - let path_source = RecursivePathSource::new(&path, source_id, gctx); + let path_source = RecursivePathSource::new(path, source_id, gctx); Self { source_id, path_source, diff --git a/src/workspace/dependency.rs b/src/workspace/dependency.rs index 9103c956e71..7f6ba4071d2 100644 --- a/src/workspace/dependency.rs +++ b/src/workspace/dependency.rs @@ -171,6 +171,30 @@ impl Dependency { } } + pub fn new_implicit_builtin(name: InternedString, path: &Path) -> CargoResult { + Ok(Dependency { + inner: Arc::new(Inner { + name, + source_id: SourceId::for_builtin(path)?, + registry_id: None, + req: OptVersionReq::Any, + kind: DepKind::Normal, + only_match_name: false, + optional: false, + public: true, + // Note that the feature specifications here will be thrown away during Unit + // generation if `-Zbuild-std-features` is enabled + features: Vec::new(), + default_features: false, + specified_req: false, + platform: None, + explicit_name_in_toml: None, + artifact: None, + opaque: true, + }), + }) + } + pub fn serialized( &self, unstable_flags: &CliUnstable, From f8cc6c08ab8d53cfc250d19d56eb63b62613d354 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 6 May 2026 16:56:48 +0100 Subject: [PATCH 11/24] test(resolve): Do not encode builtin packages in the lockfile --- src/resolver/encode.rs | 2 ++ tests/testsuite/standard_lib.rs | 35 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/src/resolver/encode.rs b/src/resolver/encode.rs index 6ef1752f265..5ce86637871 100644 --- a/src/resolver/encode.rs +++ b/src/resolver/encode.rs @@ -514,6 +514,7 @@ impl ser::Serialize for Resolve { let encodable = ids .iter() + .filter(|&p_id| !p_id.source_id().is_builtin()) .map(|&id| encodable_resolve_node(id, self, &state)) .collect::>(); @@ -607,6 +608,7 @@ fn encodable_resolve_node( None => { let mut deps = resolve .deps_not_replaced(id) + .filter(|(id, _)| !id.source_id().is_builtin()) .map(|(id, _)| encodable_package_id(id, state, resolve.version())) .collect::>(); deps.sort(); diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 137cb22035e..99ff5e9f928 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -388,6 +388,41 @@ fn check_core() { .run(); } +#[cargo_test(build_std_mock)] +fn build_std_does_not_change_lockfile() { + let setup = setup(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + "#, + ) + .file( + "src/main.rs", + r#" + fn main() { + std::custom_api(); + } + "#, + ) + .build(); + + p.cargo("generate-lockfile").run(); + let lockfile = p.read_lockfile(); + + p.cargo("build").build_std(&setup).run(); + + let build_std_lockfile = p.read_lockfile(); + assert_eq!(lockfile, build_std_lockfile); + assert!(!build_std_lockfile.contains("name = \"core\"")); + assert!(!build_std_lockfile.contains("name = \"std\"")); + assert!(!build_std_lockfile.contains("name = \"alloc\"")); +} + #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; From 6e57738b7a654de412ad6331875ed9482eacb06e Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 6 May 2026 18:19:06 +0100 Subject: [PATCH 12/24] test(package-diff): Do not emit builtin packages in status messages --- src/ops/cargo_update.rs | 9 ++++++--- tests/testsuite/standard_lib.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/ops/cargo_update.rs b/src/ops/cargo_update.rs index 0fb3881e9f3..2edb0bf621d 100644 --- a/src/ops/cargo_update.rs +++ b/src/ops/cargo_update.rs @@ -1220,7 +1220,7 @@ impl PackageDiff { pub fn new(resolve: &Resolve) -> impl Iterator { let mut changes = BTreeMap::new(); let empty = Self::default(); - for dep in resolve.iter() { + for dep in resolve.iter().filter(|id| !id.source_id().is_builtin()) { changes .entry(Self::key(dep)) .or_insert_with(|| empty.clone()) @@ -1270,14 +1270,17 @@ impl PackageDiff { // Map `(package name, package source)` to `(removed versions, added versions)`. let mut changes = BTreeMap::new(); let empty = Self::default(); - for dep in previous_resolve.iter() { + for dep in previous_resolve + .iter() + .filter(|id| !id.source_id().is_builtin()) + { changes .entry(Self::key(dep)) .or_insert_with(|| empty.clone()) .removed .push(dep); } - for dep in resolve.iter() { + for dep in resolve.iter().filter(|id| !id.source_id().is_builtin()) { changes .entry(Self::key(dep)) .or_insert_with(|| empty.clone()) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 99ff5e9f928..72d972419f8 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -423,6 +423,39 @@ fn build_std_does_not_change_lockfile() { assert!(!build_std_lockfile.contains("name = \"alloc\"")); } +#[cargo_test(build_std_mock)] +fn builtins_do_not_show_in_status_messages() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + // New lockfile + p.cargo("c") + .build_std_arg(&setup, "core") + .with_stderr_contains("[LOCKING] 1 package [..]") + .with_stderr_does_not_contain("[ADDING] core") + .run(); + + // Updating lockfile + p.cargo("add registry-dep-using-alloc") + .build_std_arg(&setup, "core,alloc") + .with_stderr_contains("[ADDING] registry-dep-using-alloc [..]") + .with_stderr_contains("[LOCKING] 1 package [..]") + .run(); +} + #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; From a734a4f2fe60b7df62e996fa885c6e895edb13bd Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 25 Feb 2026 14:35:32 +0000 Subject: [PATCH 13/24] feat(build-std): Enable implicit builtin dependencies and unit generation for them The old unit generation used to insert dependencies to every package on the set of roots std was resolved with. The new unit generation replaces dependencies with the relevant root of std, or ignores them if the dependency is not needed. This commit enables tests for the previous set of commits. --- src/compiler/standard_lib.rs | 1 + src/compiler/unit_dependencies.rs | 134 +++++++++++++++++++----------- src/ops/resolve.rs | 5 +- src/workspace/workspace.rs | 12 +++ 4 files changed, 103 insertions(+), 49 deletions(-) diff --git a/src/compiler/standard_lib.rs b/src/compiler/standard_lib.rs index 22b51d1464b..500302e3765 100644 --- a/src/compiler/standard_lib.rs +++ b/src/compiler/standard_lib.rs @@ -64,6 +64,7 @@ pub fn resolve_std<'gctx>( // TODO: Consider doing something to enforce --locked? Or to prevent the // lock file from being written, such as setting ephemeral. let mut std_ws = Workspace::new(&std_ws_manifest_path, gctx)?; + std_ws.set_is_std(true); // Don't require optional dependencies in this workspace, aka std's own // `[dev-dependencies]`. No need for us to generate a `Resolve` which has // those included because we'll never use them anyway. diff --git a/src/compiler/unit_dependencies.rs b/src/compiler/unit_dependencies.rs index 6002edfbfcc..0d2ddfe5b64 100644 --- a/src/compiler/unit_dependencies.rs +++ b/src/compiler/unit_dependencies.rs @@ -51,6 +51,8 @@ struct State<'a, 'gctx> { std_resolve: Option<&'a Resolve>, /// Like `usr_features` but for building standard library (`-Zbuild-std`). std_features: Option<&'a ResolvedFeatures>, + // The root units of any opaque dependencies present in the user resolve + opaque_roots: &'a HashMap>, /// `true` while generating the dependencies for the standard library. is_std: bool, /// The high-level operation requested by the user. @@ -91,7 +93,7 @@ pub fn build_unit_dependencies<'a, 'gctx>( resolve: &'a Resolve, features: &'a ResolvedFeatures, std_resolve: Option<&'a (Resolve, ResolvedFeatures)>, - roots: &[Unit], + roots: &[Unit], //TODO: builtins can be roots if requested on the command line scrape_units: &[Unit], std_roots: &HashMap>, intent: UserIntent, @@ -118,6 +120,7 @@ pub fn build_unit_dependencies<'a, 'gctx>( usr_features: features, std_resolve, std_features, + opaque_roots: std_roots, is_std: false, intent, target_data, @@ -128,15 +131,14 @@ pub fn build_unit_dependencies<'a, 'gctx>( }; let std_unit_deps = calc_deps_of_std(&mut state, std_roots)?; + if let Some(std_unit_deps) = std_unit_deps { + attach_std_deps(&mut state, std_unit_deps); + } deps_of_roots(roots, &mut state)?; super::links::validate_links(state.resolve(), &state.unit_dependencies)?; // Hopefully there aren't any links conflicts with the standard library? - if let Some(std_unit_deps) = std_unit_deps { - attach_std_deps(&mut state, std_roots, std_unit_deps); - } - connect_run_custom_build_deps(&mut state); // Dependencies are used in tons of places throughout the backend, many of @@ -187,38 +189,14 @@ fn calc_deps_of_std( Ok(Some(std::mem::take(&mut state.unit_dependencies))) } -/// Add the standard library units to the `unit_dependencies`. -fn attach_std_deps( - state: &mut State<'_, '_>, - std_roots: &HashMap>, - std_unit_deps: UnitGraph, -) { - // Attach the standard library as a dependency of every target unit. - let mut found = false; - for (unit, deps) in state.unit_dependencies.iter_mut() { - if !unit.kind.is_host() && !unit.mode.is_run_custom_build() { - deps.extend(std_roots[&unit.kind].iter().map(|unit| UnitDep { - unit: unit.clone(), - unit_for: UnitFor::new_normal(unit.kind), - extern_crate_name: unit.pkg.name(), - dep_name: None, - // TODO: Does this `public` make sense? - public: true, - noprelude: true, - nounused: true, - // Artificial dependency - manifest_deps: Unhashed(None), - })); - found = true; +/// Add the dependencies of standard library units to the `unit_dependencies`. +fn attach_std_deps(state: &mut State<'_, '_>, std_unit_deps: UnitGraph) { + for (unit, deps) in std_unit_deps.into_iter() { + if unit.pkg.package_id().name() == "sysroot" { + continue; } - } - // And also include the dependencies of the standard library itself. Don't - // include these if no units actually needed the standard library. - if found { - for (unit, deps) in std_unit_deps.into_iter() { - if let Some(other_unit) = state.unit_dependencies.insert(unit, deps) { - panic!("std unit collision with existing unit: {:?}", other_unit); - } + if let Some(other_unit) = state.unit_dependencies.insert(unit, deps) { + panic!("std unit collision with existing unit: {:?}", other_unit); } } } @@ -339,17 +317,48 @@ fn compute_deps( )?; ret.push(unit_dep); } else { - let unit_dep = new_unit_dep( - state, - unit, - dep_pkg, - dep_lib, - Some(manifest_deps), - dep_unit_for, - unit.kind.for_target(dep_lib), - mode, - IS_NO_ARTIFACT_DEP, - )?; + // if builtin, return from state.opaque_roots + let unit_dep = if dep_pkg_id.source_id().is_builtin() { + if unit_for.is_for_host() { + // Build scripts/proc_macros shouldn't use build-std + continue; + } + let unit = state + .opaque_roots + .get(&unit.kind.for_target(dep_lib)) + .expect("standard library was resolved for all required targets") + .iter() + .find(|&u| u.pkg.name() == dep_pkg_id.name()); + if let Some(unit) = unit { + UnitDep { + unit: unit.clone(), + unit_for: UnitFor::new_normal(unit.kind), + extern_crate_name: unit.pkg.name(), + dep_name: None, + public: true, + noprelude: true, + nounused: true, + manifest_deps: Unhashed(None), + } + } else { + // The resolve is target-independent, so may inject implicit dependencies that + // aren't actually needed for a particular target, such as if the target does + // not support std. The standard library resolve is the source of truth here. + continue; + } + } else { + new_unit_dep( + state, + unit, + dep_pkg, + dep_lib, + Some(manifest_deps), + dep_unit_for, + unit.kind.for_target(dep_lib), + mode, + IS_NO_ARTIFACT_DEP, + )? + }; ret.push(unit_dep); } @@ -365,6 +374,33 @@ fn compute_deps( } state.dev_dependency_edges.extend(dev_deps); + // Inject "test" if required + if state.gctx.cli_unstable().build_std.is_some() + && unit.mode.is_rustc_test() + && unit.target.harness() + && !unit_for.is_for_host() + { + if let Some(test) = state + .opaque_roots + .get(&unit.kind) + .expect("standard library was resolved for all required targets") + .iter() + .find(|&u| u.pkg.name() == "test") + { + let unitdep = UnitDep { + unit: test.clone(), + unit_for: UnitFor::new_normal(test.kind), + extern_crate_name: test.pkg.name(), + dep_name: None, + public: true, + noprelude: true, + nounused: true, + manifest_deps: Unhashed(None), + }; + ret.push(unitdep); + } + } + // If this target is a build script, then what we've collected so far is // all we need. If this isn't a build script, then it depends on the // build script if there is one. @@ -649,6 +685,10 @@ fn compute_deps_doc( // the documentation of the library being built. let mut ret = Vec::new(); for (id, deps) in state.deps(unit, unit_for) { + if id.source_id().is_builtin() { + // TODO: Build-std for cargo doc is not yet implemented + continue; + } let Some(dep_lib) = calc_artifact_deps(unit, unit_for, id, &deps, state, &mut ret)? else { continue; }; diff --git a/src/ops/resolve.rs b/src/ops/resolve.rs index 58f9086ca16..05a122f0689 100644 --- a/src/ops/resolve.rs +++ b/src/ops/resolve.rs @@ -520,8 +520,9 @@ pub fn resolve_with_previous<'gctx>( ResolveOpts { dev_deps, features: RequestedFeatures::CliFeatures(features), - // TODO: set this to true when build-std is enabled - inject_builtins: false, + inject_builtins: ws.gctx().cli_unstable().build_std.is_some() + && !ws.is_std() + && !member.proc_macro(), }, ) }) diff --git a/src/workspace/workspace.rs b/src/workspace/workspace.rs index 98b4e6d9d10..1c8f7cac238 100644 --- a/src/workspace/workspace.rs +++ b/src/workspace/workspace.rs @@ -101,6 +101,9 @@ pub struct Workspace<'gctx> { /// `cargo install` or `cargo package` commands. is_ephemeral: bool, + /// `true` if this is the standard library's workspace + is_std: bool, + /// `true` if this workspace should enforce optional dependencies even when /// not needed; false if this workspace should only enforce dependencies /// needed by the current configuration (such as in cargo install). In some @@ -260,6 +263,7 @@ impl<'gctx> Workspace<'gctx> { member_ids: HashSet::default(), default_members: Vec::new(), is_ephemeral: false, + is_std: false, require_optional_deps: true, loaded_packages: RefCell::new(HashMap::default()), ignore_lock: false, @@ -703,6 +707,14 @@ impl<'gctx> Workspace<'gctx> { self.is_ephemeral } + pub fn set_is_std(&mut self, is_std: bool) { + self.is_std = is_std; + } + + pub fn is_std(&self) -> bool { + self.is_std + } + pub fn require_optional_deps(&self) -> bool { self.require_optional_deps } From 8b48454705fa132105c07524050c8fcb0d725682 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Thu, 6 Aug 2026 17:29:43 +0100 Subject: [PATCH 14/24] test(unit-generation): no-std build-std --- tests/testsuite/standard_lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 72d972419f8..babb8107121 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -553,6 +553,9 @@ fn build_std_with_no_arg_for_core_only_target() { "#]] .unordered(), ) + .with_stderr_does_not_contain( + "[RUNNING] `[..]rustc --crate-name alloc [..]--target aarch64-unknown-none[..]`", + ) .run(); } From e5fc4a937b810474bb7666260d081f9bd8fcf695 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 11:21:10 +0100 Subject: [PATCH 15/24] test(build-std): Fix shared_std test This test's assertion was completely broken because of the leading spaces and use of `...` which doesn't work with stderr_does_not_contain. See the comments in this test for additional details of shared dependencies on why the test cannot be fixed right now. It is feasibly possible for Cargo to share the user dep_test dependency and the build-std dep_test, but this is complex as written as dep_test gains an implicit std dependency while the mock-std depends on it. It may be easier to share them with explicit builtin dependencies, but the situation is complex due to -Zforce-unstable-if-unmarked and prone to breakage due to RUSTC_BOOTSTRAP=1. --- tests/testsuite/standard_lib.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index babb8107121..b0e3559cb26 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -265,7 +265,11 @@ fn shared_std_dependency_rebuild() { [build-dependencies] dep_test = {{ path = \"{}/tests/testsuite/mock-std/dep_test\" }} + + [dependencies] + dep_test = {{ path = \"{}/tests/testsuite/mock-std/dep_test\" }} ", + manifest_dir.replace('\\', "/"), manifest_dir.replace('\\', "/") ) .as_str(), @@ -288,27 +292,30 @@ fn shared_std_dependency_rebuild() { ) .build(); + // One build each for the: + // - build-std build + // - build-dependency (for the host, with the sysroot std) + // - dependency (for the host, with build-std std) p.cargo("build -v") .build_std(&setup) - .target_host() .with_stderr_data(str![[r#" ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... +[RUNNING] `[..] rustc --crate-name dep_test [..]` +... "#]]) .run(); + // Sanity check that all artifacts are reused. + // TODO: This test used to test that the build-dependency would be shared between `--target=host` and + // non-`--target` invocations, but that isn't currently testable as RUSTFLAGS set in this file's + // test harness apply to the non-cross-compile mode. p.cargo("build -v") .build_std(&setup) - .with_stderr_does_not_contain(str![[r#" - ... - [RUNNING] `[..] rustc --crate-name dep_test [..]` - ... - [RUNNING] `[..] rustc --crate-name dep_test [..]` - ... - "#]]) + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name dep_test [..]`") .run(); } From 13f0c62c47e1b01307b26ec8441b60182ce7b6d5 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 16:25:12 +0100 Subject: [PATCH 16/24] test(build-std): builtins as roots --- tests/testsuite/standard_lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index b0e3559cb26..5abd354007e 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -972,3 +972,16 @@ fn std_build_script_metadata_propagate_to_user() { p.cargo("check").build_std(&setup).target_host().run(); } + +#[ignore = "Fails with rustc errors. builtin roots are not replaced."] +#[cargo_test(build_std_mock)] +fn std_build_dash_p() { + let setup = setup(); + + let p = project().file("src/main.rs", "fn main() {}").build(); + p.cargo("b -v -p std") + .build_std(&setup) + .with_stderr_contains("[RUNNING] `[..] rustc --crate-name std [..]`") + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name foo [..]`") + .run(); +} From c640bb9eb4331f37b627afbbfd0346034b7aadcd Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 16:34:08 +0100 Subject: [PATCH 17/24] feat(unit generation): Replace builtin roots with std roots --- src/compiler/unit_dependencies.rs | 2 +- src/ops/cargo_compile/mod.rs | 38 +++++++++++++++++++++---------- tests/testsuite/standard_lib.rs | 24 ++++++++++++++++++- 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/compiler/unit_dependencies.rs b/src/compiler/unit_dependencies.rs index 0d2ddfe5b64..7a9605dccb0 100644 --- a/src/compiler/unit_dependencies.rs +++ b/src/compiler/unit_dependencies.rs @@ -93,7 +93,7 @@ pub fn build_unit_dependencies<'a, 'gctx>( resolve: &'a Resolve, features: &'a ResolvedFeatures, std_resolve: Option<&'a (Resolve, ResolvedFeatures)>, - roots: &[Unit], //TODO: builtins can be roots if requested on the command line + roots: &[Unit], scrape_units: &[Unit], std_roots: &HashMap>, intent: UserIntent, diff --git a/src/ops/cargo_compile/mod.rs b/src/ops/cargo_compile/mod.rs index 0e16f487fe0..ff26fbb2581 100644 --- a/src/ops/cargo_compile/mod.rs +++ b/src/ops/cargo_compile/mod.rs @@ -473,18 +473,6 @@ pub fn create_bcx<'a, 'gctx>( // Should be fine as the loop iterate is independent of target selection selected_dep_kinds = curr_selected_dep_kinds; - if let Some(args) = target_rustc_crate_types { - override_rustc_crate_types(&mut targeted_root_units, args, interner)?; - } - - let should_scrape = - build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples; - let targeted_scrape_units = if should_scrape { - generator.generate_scrape_units(&targeted_root_units)? - } else { - Vec::new() - }; - let std_roots = if let Some(crates) = gctx.cli_unstable().build_std.as_ref() { let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap(); standard_lib::generate_std_roots( @@ -502,6 +490,32 @@ pub fn create_bcx<'a, 'gctx>( Default::default() }; + // Update the roots with std roots where needed + for root in targeted_root_units.iter_mut() { + if root.pkg.package_id().source_id().is_builtin() { + let unit = std_roots + .get(&root.kind) + .expect("standard library was resolved for all required targets") + .iter() + .find(|&u| u.pkg.name() == root.pkg.name()) + .expect("no std root found for requested package"); + //TODO: Handle and test the here properly + *root = unit.clone(); + } + } + + if let Some(args) = target_rustc_crate_types { + override_rustc_crate_types(&mut targeted_root_units, args, interner)?; + } + + let should_scrape = + build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples; + let targeted_scrape_units = if should_scrape { + generator.generate_scrape_units(&targeted_root_units)? + } else { + Vec::new() + }; + unit_graph.extend(build_unit_dependencies( ws, &pkg_set, diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 5abd354007e..320b2d723cc 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -973,7 +973,6 @@ fn std_build_script_metadata_propagate_to_user() { p.cargo("check").build_std(&setup).target_host().run(); } -#[ignore = "Fails with rustc errors. builtin roots are not replaced."] #[cargo_test(build_std_mock)] fn std_build_dash_p() { let setup = setup(); @@ -984,4 +983,27 @@ fn std_build_dash_p() { .with_stderr_contains("[RUNNING] `[..] rustc --crate-name std [..]`") .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name foo [..]`") .run(); + + p.cargo("b -v -p foo") + .build_std(&setup) + .with_stderr_contains("[RUNNING] `[..] rustc --crate-name foo [..]`") + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name std [..]`") + .run(); + + p.cargo("clean -v -p std") + .build_std(&setup) + .with_stderr_contains("[REMOVED] [FILE_NUM] files, [FILE_SIZE]B total") + .run(); + + p.cargo("b -v -p foo") + .build_std(&setup) + .with_stderr_data(str![[r#" +... +[RUNNING] `[..] rustc --crate-name std [..]` +... +[RUNNING] `[..] rustc --crate-name foo [..]` +... +"#]]) + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name core [..]`") + .run(); } From a48a9d0c44f3f64b3b109f6d2828de30f780e4b4 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 16:49:36 +0100 Subject: [PATCH 18/24] test(metadata): Do not emit builtins with cargo metadata The ideal behaviour is probably to emit all packages, but currently only the roots of std are emitted. A future PR will change this. --- tests/testsuite/standard_lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 320b2d723cc..6fac0518e4a 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -463,6 +463,32 @@ fn builtins_do_not_show_in_status_messages() { .run(); } +#[ignore = "implicit builtin dependencies are currently emitted by cargo metadata, but not the whole unit graph"] +#[cargo_test(build_std_mock)] +fn builtins_do_not_show_in_metadata() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + p.cargo("metadata") + .build_std_arg(&setup, "core") + .with_stdout_contains("[..]registry-dep-using-core[..]") + .with_stdout_does_not_contain("[..]builtin[..]") + .run(); +} + #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; From a288440af3f1e65f673431d63dadcb4a455d1eb4 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 16:55:19 +0100 Subject: [PATCH 19/24] fix(metadata): Filter out builtin packages --- src/ops/cargo_metadata.rs | 8 ++++++-- tests/testsuite/standard_lib.rs | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ops/cargo_metadata.rs b/src/ops/cargo_metadata.rs index b283e5dd604..9495cf04862 100644 --- a/src/ops/cargo_metadata.rs +++ b/src/ops/cargo_metadata.rs @@ -231,8 +231,12 @@ fn build_resolve_graph_r( let deps = { let mut dep_metadatas = Vec::new(); - let iter = resolve.deps(pkg_id).filter(|(_dep_id, deps)| { - if requested_kinds == [CompileKind::Host] { + let iter = resolve.deps(pkg_id).filter(|(dep_id, deps)| { + if dep_id.source_id().is_builtin() { + //TODO: cargo metadata behaviour is an unresolved question on the explicit builtin + //dependencies RFC + false + } else if requested_kinds == [CompileKind::Host] { true } else { requested_kinds.iter().any(|kind| { diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 6fac0518e4a..48471cf1228 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -463,7 +463,6 @@ fn builtins_do_not_show_in_status_messages() { .run(); } -#[ignore = "implicit builtin dependencies are currently emitted by cargo metadata, but not the whole unit graph"] #[cargo_test(build_std_mock)] fn builtins_do_not_show_in_metadata() { let setup = setup(); From 4e9354a482d0132c3dc902ced9eb098b028b8846 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 19:21:41 +0100 Subject: [PATCH 20/24] test(build-std): Do not emit builtins with cargo tree --- tests/testsuite/standard_lib.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 48471cf1228..457fa3b5abe 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -488,6 +488,32 @@ fn builtins_do_not_show_in_metadata() { .run(); } +#[ignore = "implicit builtin dependencies are currently emitted by cargo tree"] +#[cargo_test(build_std_mock)] +fn builtins_do_not_show_in_tree() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + p.cargo("tree") + .build_std_arg(&setup, "core") + .with_stdout_contains("[..]registry-dep-using-core[..]") + .with_stdout_does_not_contain("[..]builtin[..]") + .run(); +} + #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; From 6d6fe8f2e9de0e27be388818e2b68982e229b097 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 19:24:46 +0100 Subject: [PATCH 21/24] fix(tree): Don't emit builtins --- src/ops/cargo_tree/graph.rs | 5 ++++- tests/testsuite/standard_lib.rs | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ops/cargo_tree/graph.rs b/src/ops/cargo_tree/graph.rs index 10ade176010..511721d1559 100644 --- a/src/ops/cargo_tree/graph.rs +++ b/src/ops/cargo_tree/graph.rs @@ -430,7 +430,10 @@ fn add_pkg( let from_index = graph.add_node(node); // Compute the dep name map which is later used for foo/bar feature lookups. let mut dep_name_map: HashMap> = HashMap::default(); - let mut deps: Vec<_> = resolve.deps(package_id).collect(); + let mut deps: Vec<_> = resolve + .deps(package_id) + .filter(|(dep_id, _)| !dep_id.source_id().is_builtin()) + .collect(); deps.sort_unstable_by_key(|(dep_id, _)| *dep_id); let show_all_targets = opts.target == super::Target::All; for (dep_id, deps) in deps { diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 457fa3b5abe..9661de5fd9e 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -488,7 +488,6 @@ fn builtins_do_not_show_in_metadata() { .run(); } -#[ignore = "implicit builtin dependencies are currently emitted by cargo tree"] #[cargo_test(build_std_mock)] fn builtins_do_not_show_in_tree() { let setup = setup(); From 12d0b0877be7d3d17df655a29d2155cd855d928a Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 19:29:51 +0100 Subject: [PATCH 22/24] test(vendor): builtins shouldn't be vendored --- tests/testsuite/standard_lib.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 9661de5fd9e..8b2cd56c396 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -513,6 +513,38 @@ fn builtins_do_not_show_in_tree() { .run(); } +#[ignore = "vendoring tries to vendor builtins, and fails"] +#[cargo_test(build_std_mock)] +fn builtins_are_not_vendored() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + p.cargo("vendor --respect-source-config") + .build_std_arg(&setup, "core") + .run(); + + assert!( + p.root() + .join("vendor/registry-dep-using-core/Cargo.toml") + .is_file() + ); + assert!(!p.root().join("vendor/core").exists()); + assert!(!p.root().join("vendor/compiler_builtins").exists()); +} + #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; From 08574b8ec4776d4cf42affcc5728ecdb19d655b9 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 19:34:26 +0100 Subject: [PATCH 23/24] fix(vendor): Don't vendor builtins --- src/ops/cargo_vendor.rs | 4 ++-- tests/testsuite/standard_lib.rs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ops/cargo_vendor.rs b/src/ops/cargo_vendor.rs index 9cd5d7a06f8..18307a78ec2 100644 --- a/src/ops/cargo_vendor.rs +++ b/src/ops/cargo_vendor.rs @@ -154,10 +154,10 @@ fn sync( .with_context(|| format!("failed to load lockfile for {}", ws.root().display()))?; packages - .get_many(resolve.iter()) + .get_many(resolve.iter().filter(|pkg| !pkg.source_id().is_builtin())) .with_context(|| format!("failed to download packages for {}", ws.root().display()))?; - for pkg in resolve.iter() { + for pkg in resolve.iter().filter(|pkg| !pkg.source_id().is_builtin()) { let sid = source_replacement_cache.get(pkg.source_id())?; // Don't vendor path crates since they're already in the repository diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 8b2cd56c396..6498e438917 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -513,7 +513,6 @@ fn builtins_do_not_show_in_tree() { .run(); } -#[ignore = "vendoring tries to vendor builtins, and fails"] #[cargo_test(build_std_mock)] fn builtins_are_not_vendored() { let setup = setup(); From cc1e02bb394ac47aba53764b186499bfb1b79151 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 11 Aug 2026 14:31:48 +0100 Subject: [PATCH 24/24] fixup: test(resolve): Do not encode builtin packages in the lockfile Added new assertions for deps of std roots --- tests/testsuite/standard_lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 6498e438917..e4839780aee 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -425,9 +425,15 @@ fn build_std_does_not_change_lockfile() { let build_std_lockfile = p.read_lockfile(); assert_eq!(lockfile, build_std_lockfile); + + // Demonstrate the positive check actually works + assert!(build_std_lockfile.contains("name = \"foo\"")); + assert!(!build_std_lockfile.contains("name = \"core\"")); assert!(!build_std_lockfile.contains("name = \"std\"")); assert!(!build_std_lockfile.contains("name = \"alloc\"")); + assert!(!build_std_lockfile.contains("name = \"object\"")); + assert!(!build_std_lockfile.contains("name = \"libc\"")); } #[cargo_test(build_std_mock)]