From 9dce7f7e53b81b6992efb26e97063795a0784aed Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 21 Jul 2026 16:33:30 +0100 Subject: [PATCH 01/10] 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..d70205eb161 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1036,3 +1036,12 @@ failed to select a version for `F` which could resolve this conflict "#]] ); } + +#[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"])); +} From c7d10193d65c41c309a72b09130f76614eb54059 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 25 Aug 2026 17:53:57 +0100 Subject: [PATCH 02/10] feat(sourceid): Allow creating Builtin SourceIds --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/cargo-util-schemas/Cargo.toml | 2 +- .../cargo-util-schemas/src/core/source_kind.rs | 7 +++++++ crates/resolver-tests/src/helpers.rs | 17 +++++++++++++++++ crates/resolver-tests/tests/resolve.rs | 14 ++++++++------ src/workspace/source_id.rs | 10 ++++++++++ 7 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63d7c67034d..b6d04b8a045 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,7 +581,7 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.14.4" +version = "0.15.0" dependencies = [ "jiff", "schemars", diff --git a/Cargo.toml b/Cargo.toml index 118765b0170..de068415902 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.15", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.12.0", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.33", path = "crates/cargo-util" } -cargo-util-schemas = { version = "0.14.4", path = "crates/cargo-util-schemas" } +cargo-util-schemas = { version = "0.15.0", path = "crates/cargo-util-schemas" } cargo-util-terminal = { version = "0.1.3", 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 489c2b52ce3..e17feac3275 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.4" +version = "0.15.0" rust-version = "1.98" # MSRV:1 edition.workspace = true license.workspace = true 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..00c0bde2b68 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -87,6 +87,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 +118,13 @@ 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(&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 d70205eb161..0a7aa79f6d4 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, }; @@ -1039,9 +1039,11 @@ failed to select a version for `F` which could resolve this conflict #[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"])); } 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 584369a43161f55698b5c378d70a33485b1d71f2 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 26 Aug 2026 11:19:42 +0100 Subject: [PATCH 03/10] test(resolver): Match builtin dependencies to builtin packages --- crates/resolver-tests/src/helpers.rs | 11 +++++++++++ crates/resolver-tests/tests/resolve.rs | 7 +++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index 00c0bde2b68..604d09a1018 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -87,6 +87,7 @@ impl, U: AsRef> ToPkgId for (T, U) { } } +#[derive(Copy, Clone)] pub struct BuiltinPid { pub name: &'static str, } @@ -232,6 +233,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); @@ -252,6 +257,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 0a7aa79f6d4..30858705cfd 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; @@ -1042,8 +1043,10 @@ 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 builtin_dep = dep_builtin("core"); + + // Fails + //let res = resolve(vec![builtin_dep], ®).unwrap(); //assert_same(&res, &names(&["root", "core"])); } From 5d56894928e9ba774a3d6d565a7654193e042d2c Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 26 Aug 2026 11:35:09 +0100 Subject: [PATCH 04/10] feat(sourceid): Allow matching builtin packages with dependencies --- crates/resolver-tests/tests/resolve.rs | 7 +++---- src/resolver/encode.rs | 2 +- src/workspace/source_id.rs | 5 +++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 30858705cfd..ce6c599c112 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -10,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] @@ -1045,8 +1045,7 @@ fn test_builtin_dependency() { let builtin_dep = dep_builtin("core"); - // Fails - //let res = resolve(vec![builtin_dep], ®).unwrap(); + let res = resolve(vec![builtin_dep], ®).unwrap(); - //assert_same(&res, &names(&["root", "core"])); + assert_same(&res, &names!("root", core)); } 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 9716028de9b19178c788bde5dc74e912224a8820 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 17:04:12 +0100 Subject: [PATCH 05/10] test(resolver): Add negative resolver tests --- crates/resolver-tests/tests/resolve.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index ce6c599c112..7c890596d11 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1049,3 +1049,16 @@ fn test_builtin_dependency() { 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 b35e1c3a10f8debced3d20bf8247c0c330d8735a Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 7 Aug 2026 17:35:03 +0100 Subject: [PATCH 06/10] test(resolver): Test for implicit builtin deps --- crates/resolver-tests/src/lib.rs | 23 ++++++++++++++++++++--- crates/resolver-tests/tests/resolve.rs | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index cad66c896d2..cc7ea464f09 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_gctx_implicit_deps_raw( deps.clone(), registry, root_pkg_id, &GlobalContext::default().unwrap(), + &[], ); match resolve { @@ -115,20 +116,36 @@ fn collect_features(resolve: &Resolve) -> Vec<(PackageId, Vec)> .collect() } +pub fn resolve_with_implicit_builtins( + deps: Vec, + registry: &[Summary], + implicit_builtin_deps: &[Dependency], +) -> CargoResult { + let gctx = GlobalContext::default().unwrap(); + resolve_with_gctx_implicit_deps_raw( + deps, + registry, + pkg_id("root"), + &gctx, + implicit_builtin_deps, + ) +} + pub fn resolve_with_global_context( deps: Vec, registry: &[Summary], gctx: &GlobalContext, ) -> CargoResult)>> { - let resolve = resolve_with_global_context_raw(deps, registry, pkg_id("root"), gctx)?; + let resolve = resolve_with_gctx_implicit_deps_raw(deps, registry, pkg_id("root"), gctx, &[])?; Ok(collect_features(&resolve)) } -pub fn resolve_with_global_context_raw( +pub fn resolve_with_gctx_implicit_deps_raw( deps: Vec, registry: &[Summary], root_pkg_id: PackageId, gctx: &GlobalContext, + _implicit_builtin_deps: &[Dependency], ) -> CargoResult { struct MyRegistry<'a> { list: &'a [Summary], diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 7c890596d11..36470bc207f 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -2,6 +2,7 @@ use cargo::util::GlobalContext; use cargo::workspace::Dependency; use cargo::workspace::dependency::DepKind; use resolver_tests::helpers::dep_builtin; +use resolver_tests::resolve_with_implicit_builtins; use snapbox::assert_data_eq; use snapbox::str; @@ -1062,3 +1063,23 @@ 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 injected_builtins() { + let core = BuiltinPid { name: "core" }; + let core = pkg!(core); + let compiler_builtins = BuiltinPid { + name: "compiler_builtins", + }; + let compiler_builtins = pkg!(compiler_builtins); + + let reg = registry(vec![core, compiler_builtins]); + + let resolve = resolve_with_implicit_builtins(Vec::new(), ®, &[]).unwrap(); + + let root_deps = resolve + .deps(pkg_id("root")) + .map(|(pkg_id, _)| pkg_id) + .collect::>(); + assert_same(&root_deps, &[]); +} From 36058f198639a334513c780b9f5ef465f2518561 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 1 Sep 2026 16:23:13 +0100 Subject: [PATCH 07/10] feat(resolver): Inject builtin dependencies --- crates/resolver-tests/src/lib.rs | 5 +++-- crates/resolver-tests/tests/resolve.rs | 26 +++++++++++++++++++++++--- src/compiler/standard_lib.rs | 10 +++++++--- src/ops/resolve.rs | 4 ++++ src/resolver/dep_cache.rs | 12 +++++++++++- src/resolver/mod.rs | 9 ++++++++- src/workspace/dependency.rs | 23 +++++++++++++++++++++++ src/workspace/source_id.rs | 8 ++++---- 8 files changed, 83 insertions(+), 14 deletions(-) diff --git a/crates/resolver-tests/src/lib.rs b/crates/resolver-tests/src/lib.rs index cc7ea464f09..e960e7d4e74 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -140,12 +140,12 @@ pub fn resolve_with_global_context( Ok(collect_features(&resolve)) } -pub fn resolve_with_gctx_implicit_deps_raw( +fn resolve_with_gctx_implicit_deps_raw( deps: Vec, registry: &[Summary], root_pkg_id: PackageId, gctx: &GlobalContext, - _implicit_builtin_deps: &[Dependency], + implicit_builtin_deps: &[Dependency], ) -> CargoResult { struct MyRegistry<'a> { list: &'a [Summary], @@ -222,6 +222,7 @@ pub fn resolve_with_gctx_implicit_deps_raw( &version_prefs, ResolveVersion::with_rust_version(None), gctx, + implicit_builtin_deps, ); // The largest test in our suite takes less then 30 secs. diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 36470bc207f..3fda05e20ee 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1,4 +1,5 @@ use cargo::util::GlobalContext; +use cargo::util::interning::InternedString; use cargo::workspace::Dependency; use cargo::workspace::dependency::DepKind; use resolver_tests::helpers::dep_builtin; @@ -1073,13 +1074,32 @@ fn injected_builtins() { }; let compiler_builtins = pkg!(compiler_builtins); - let reg = registry(vec![core, compiler_builtins]); + let reg = registry(vec![core.clone(), compiler_builtins.clone()]); - let resolve = resolve_with_implicit_builtins(Vec::new(), ®, &[]).unwrap(); + let mut deps = vec![]; + deps.push( + Dependency::new_implicit_builtin( + InternedString::new("core"), + &core.source_id().local_path().unwrap(), + ) + .unwrap(), + ); + deps.push( + Dependency::new_implicit_builtin( + InternedString::new("compiler_builtins"), + &core.source_id().local_path().unwrap(), + ) + .unwrap(), + ); + + let resolve = resolve_with_implicit_builtins(Vec::new(), ®, &deps).unwrap(); let root_deps = resolve .deps(pkg_id("root")) .map(|(pkg_id, _)| pkg_id) .collect::>(); - assert_same(&root_deps, &[]); + assert_same( + &root_deps, + &[core.package_id(), compiler_builtins.package_id()], + ); } diff --git a/src/compiler/standard_lib.rs b/src/compiler/standard_lib.rs index ba5e1bc12f9..d3d05d5dd1b 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; 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. @@ -217,7 +221,7 @@ fn generate_roots( Ok(()) } -fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult { +pub(crate) fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult { if let Some(s) = target_data.gctx.get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") { return Ok(s.into()); } diff --git a/src/ops/resolve.rs b/src/ops/resolve.rs index aa5821210dd..def98e9245d 100644 --- a/src/ops/resolve.rs +++ b/src/ops/resolve.rs @@ -528,6 +528,9 @@ pub fn resolve_with_previous<'gctx>( let replace = lock_replacements(ws, previous, &keep); + //TODO: Enable implicit builtin dependencies for `-Zbuild-std` once builtins are fully implemented + let implicit_builtin_deps = &[]; + let mut resolved = resolver::resolve( &summaries, &replace, @@ -535,6 +538,7 @@ pub fn resolve_with_previous<'gctx>( &version_prefs, ResolveVersion::with_rust_version(ws.lowest_rust_version()), ws.gctx(), + implicit_builtin_deps, )?; let patches = registry.patches().values().flat_map(|v| v.iter()); diff --git a/src/resolver/dep_cache.rs b/src/resolver/dep_cache.rs index f2a928fe231..756e3b7857d 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 !candidate.source_id().is_builtin() { + 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 ce1ca941efc..b0f993ca595 100644 --- a/src/resolver/mod.rs +++ b/src/resolver/mod.rs @@ -129,12 +129,19 @@ pub fn resolve( version_prefs: &VersionPreferences, resolve_version: ResolveVersion, gctx: &GlobalContext, + implicit_builtin_deps: &[Dependency], ) -> CargoResult { let first_version = gctx .cli_unstable() .direct_minimal_versions .then_some(VersionOrdering::MinimumVersionsFirst); - let mut registry = RegistryQueryer::new(registry, replacements, version_prefs); + + 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/workspace/dependency.rs b/src/workspace/dependency.rs index b4f4b9c8095..e9fc65caf83 100644 --- a/src/workspace/dependency.rs +++ b/src/workspace/dependency.rs @@ -166,6 +166,29 @@ 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, + // Build-std does not currently resolve features - any feature specifications here + // will be thrown away during Unit generation + features: Vec::new(), + default_features: false, + specified_req: false, + platform: None, + explicit_name_in_toml: None, + artifact: None, + }), + }) + } + pub fn serialized( &self, unstable_flags: &CliUnstable, diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index 6c8d043e969..280b83ebda4 100644 --- a/src/workspace/source_id.rs +++ b/src/workspace/source_id.rs @@ -360,11 +360,11 @@ impl SourceId { /// Returns the local path if this is a path dependency. pub fn local_path(self) -> Option { - if self.inner.kind != SourceKind::Path { - return None; + if let SourceKind::Path | SourceKind::Builtin = self.inner.kind { + Some(self.inner.url.to_file_path().unwrap()) + } else { + None } - - Some(self.inner.url.to_file_path().unwrap()) } pub fn kind(&self) -> &SourceKind { From cba047fd12ed586dd95fc34224051cfe95bb7490 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 26 Aug 2026 15:34:46 +0100 Subject: [PATCH 08/10] test(sources): Satisfy dependencies that are both opaque and builtin --- src/sources/builtin.rs | 18 ++++++++++++++++++ src/sources/mod.rs | 1 + 2 files changed, 19 insertions(+) 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..8e0c716033a --- /dev/null +++ b/src/sources/builtin.rs @@ -0,0 +1,18 @@ +#[cfg(test)] +mod test { + use std::path::Path; + + use crate::{GlobalContext, workspace::Dependency}; + + #[test] + fn builtin_source() { + let gctx = GlobalContext::default().unwrap(); + let mock_std_root = + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); + + let dep = Dependency::new_implicit_builtin("core".into(), &mock_std_root).unwrap(); + + // No valid source SourceKind::Builtin + //let source = dep.source_id().load(&gctx).unwrap(); + } +} diff --git a/src/sources/mod.rs b/src/sources/mod.rs index 5d92c92770b..0a4d8a9efc7 100644 --- a/src/sources/mod.rs +++ b/src/sources/mod.rs @@ -37,6 +37,7 @@ pub use self::registry::{ }; pub use self::replaced::ReplacedSource; +pub mod builtin; pub mod config; pub mod directory; pub mod git; From 294992213fff4cb2200f586857f99be2b98f11ad Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 26 Aug 2026 14:16:54 +0100 Subject: [PATCH 09/10] feat(dependencies): Mark builtin dependencies as opaque This is technically not necessary - all Builtins are currently opaque and vice-versa. However I feel separating the concern of opaque dependencies from that of builtins is important here even if opaque dependencies are not yet supported more generally. --- crates/resolver-tests/tests/resolve.rs | 6 +++++- src/sources/builtin.rs | 1 + src/workspace/dependency.rs | 11 +++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 3fda05e20ee..b8b843ccf63 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1096,7 +1096,11 @@ fn injected_builtins() { let root_deps = resolve .deps(pkg_id("root")) - .map(|(pkg_id, _)| pkg_id) + .map(|(pkg_id, deps)| { + assert_eq!(deps.len(), 1); + assert!(deps.iter().all(Dependency::is_opaque)); + pkg_id + }) .collect::>(); assert_same( &root_deps, diff --git a/src/sources/builtin.rs b/src/sources/builtin.rs index 8e0c716033a..4f71546e6ec 100644 --- a/src/sources/builtin.rs +++ b/src/sources/builtin.rs @@ -11,6 +11,7 @@ mod test { Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); let dep = Dependency::new_implicit_builtin("core".into(), &mock_std_root).unwrap(); + assert!(dep.is_opaque()); // No valid source SourceKind::Builtin //let source = dep.source_id().load(&gctx).unwrap(); diff --git a/src/workspace/dependency.rs b/src/workspace/dependency.rs index e9fc65caf83..cb0dc1d9b46 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,8 @@ impl Dependency { platform: None, explicit_name_in_toml: None, artifact: None, + // All deps on builtin packages are opaque, and vice versa + opaque: source_id.is_builtin(), }), } } @@ -185,6 +191,7 @@ impl Dependency { platform: None, explicit_name_in_toml: None, artifact: None, + opaque: true, }), }) } @@ -435,6 +442,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 From f1e6665a5dc11c57e33c34f0358a68c49ddd232b Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Fri, 21 Aug 2026 12:05:43 +0100 Subject: [PATCH 10/10] feat(sources): Add builtin source, which generates opaque summaries --- src/sources/builtin.rs | 126 ++++++++++++++++++++++++++++++++++++- src/sources/mod.rs | 1 + src/sources/path.rs | 5 +- src/workspace/source_id.rs | 13 +++- src/workspace/summary.rs | 25 ++++++++ 5 files changed, 164 insertions(+), 6 deletions(-) diff --git a/src/sources/builtin.rs b/src/sources/builtin.rs index 4f71546e6ec..665ae78e55d 100644 --- a/src/sources/builtin.rs +++ b/src/sources/builtin.rs @@ -1,8 +1,119 @@ +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_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.source_id().is_builtin() { + // 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); + } +} + #[cfg(test)] mod test { use std::path::Path; - use crate::{GlobalContext, workspace::Dependency}; + use crate::{GlobalContext, sources::IndexSummary, workspace::Dependency}; #[test] fn builtin_source() { @@ -13,7 +124,16 @@ mod test { let dep = Dependency::new_implicit_builtin("core".into(), &mock_std_root).unwrap(); assert!(dep.is_opaque()); - // No valid source SourceKind::Builtin - //let source = dep.source_id().load(&gctx).unwrap(); + let source = dep.source_id().load(&gctx).unwrap(); + let results = + crate::util::block_on(source.query_vec(&dep, crate::sources::source::QueryKind::Exact)) + .unwrap(); + + assert_eq!(results.len(), 1); + let result = results[0].clone(); + if let IndexSummary::Candidate(s) = result { + assert!(dep.matches(&s)); + assert!(s.dependencies().is_empty()); + } } } diff --git a/src/sources/mod.rs b/src/sources/mod.rs index 0a4d8a9efc7..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; 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/source_id.rs b/src/workspace/source_id.rs index 280b83ebda4..f26d48173a0 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 }