From 7c80afba59e1dd670ca6958f86eb5a212556b58b Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 25 Feb 2026 14:32:15 +0000 Subject: [PATCH 1/7] Add new builtin SourceKind --- crates/cargo-util-schemas/src/core/source_kind.rs | 7 +++++++ src/cargo/core/source_id.rs | 13 +++++++++++++ 2 files changed, 20 insertions(+) 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/src/cargo/core/source_id.rs b/src/cargo/core/source_id.rs index dade72b5a4b..51f566dc598 100644 --- a/src/cargo/core/source_id.rs +++ b/src/cargo/core/source_id.rs @@ -176,6 +176,10 @@ impl SourceId { let url = url.into_url()?; SourceId::new(SourceKind::Path, url, None) } + "builtin" => { + let url = url.into_url()?; + SourceId::new(SourceKind::Builtin, url, None) + } kind => Err(anyhow::format_err!("unsupported source protocol: {}", kind)), } } @@ -433,6 +437,14 @@ impl SourceId { .expect("path sources cannot be remote"); Ok(Box::new(DirectorySource::new(&path, self, gctx))) } + SourceKind::Builtin => { + let path = self + .inner + .url + .to_file_path() + .expect("builtin sources should not be remote"); + Ok(Box::new(PathSource::new(&path, self, gctx))) + } } } @@ -679,6 +691,7 @@ impl fmt::Display for SourceId { } SourceKind::LocalRegistry => write!(f, "registry `{}`", url_display(&self.inner.url)), SourceKind::Directory => write!(f, "dir {}", url_display(&self.inner.url)), + SourceKind::Builtin => write!(f, "builtin {}", url_display(&self.inner.url)), } } } From f2a5690e76b28f0a0a59545672b11b9b949569c0 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 25 Feb 2026 14:32:30 +0000 Subject: [PATCH 2/7] Inject builtin deps & Summarys while resolving --- src/cargo/core/compiler/standard_lib.rs | 1 + src/cargo/core/dependency.rs | 32 +++++++++++++++++++++ src/cargo/core/registry.rs | 33 ++++++++++++++++++++- src/cargo/core/resolver/context.rs | 4 +++ src/cargo/core/resolver/dep_cache.rs | 38 +++++++++++++++++++++++-- src/cargo/core/resolver/encode.rs | 3 +- src/cargo/core/resolver/mod.rs | 3 +- src/cargo/core/resolver/types.rs | 2 +- src/cargo/core/source_id.rs | 19 +++++++++++++ src/cargo/core/summary.rs | 5 ++++ src/cargo/ops/cargo_compile/mod.rs | 1 + src/cargo/ops/cargo_output_metadata.rs | 1 + src/cargo/ops/cargo_package/mod.rs | 1 + src/cargo/ops/cargo_update.rs | 3 ++ src/cargo/ops/fix/mod.rs | 1 + src/cargo/ops/resolve.rs | 12 ++++++-- src/cargo/ops/tree/mod.rs | 1 + src/cargo/util/command_prelude.rs | 1 + 18 files changed, 152 insertions(+), 9 deletions(-) diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index 45a313a921d..d47400ae95b 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -99,6 +99,7 @@ pub fn resolve_std<'gctx>( HasDevUnits::No, crate::core::resolver::features::ForceAllTargets::No, dry_run, + false, )?; debug_assert_eq!(resolve.specs_and_features.len(), 1); Ok(( diff --git a/src/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index db421dd0c24..5806aa6d3b2 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/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 be resolved with a separate resolver run, and handled + // by unit generation. + opaque: bool, } #[derive(Serialize)] @@ -162,6 +166,30 @@ impl Dependency { platform: None, explicit_name_in_toml: None, artifact: None, + opaque: false, + }), + } + } + + pub fn new_injected_builtin(name: InternedString) -> Dependency { + assert!(!name.is_empty()); + Dependency { + inner: Arc::new(Inner { + name, + source_id: SourceId::new_builtin(&name).expect("package name is valid url"), + registry_id: None, + req: OptVersionReq::Any, + kind: DepKind::Normal, + only_match_name: true, + optional: false, + public: true, + features: Vec::new(), + default_features: true, + specified_req: false, + platform: None, + explicit_name_in_toml: None, + artifact: None, + opaque: true, }), } } @@ -455,6 +483,10 @@ impl Dependency { pub(crate) fn maybe_lib(&self) -> bool { self.artifact().map(|a| a.is_lib).unwrap_or(true) } + + pub fn is_opaque(&self) -> bool { + self.inner.opaque + } } /// The presence of an artifact turns an ordinary dependency into an Artifact dependency. diff --git a/src/cargo/core/registry.rs b/src/cargo/core/registry.rs index 99f1d857ac0..dcd12673903 100644 --- a/src/cargo/core/registry.rs +++ b/src/cargo/core/registry.rs @@ -9,7 +9,7 @@ //! The former is just one kind of source, //! while the latter involves operations on the registry Web API. -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::task::{Poll, ready}; use crate::core::{Dependency, PackageId, PackageSet, Patch, SourceId, Summary}; @@ -24,6 +24,7 @@ use crate::util::{CanonicalUrl, GlobalContext}; use annotate_snippets::Level; use anyhow::Context as _; use itertools::Itertools; +use semver::Version; use tracing::{debug, trace}; use url::Url; @@ -724,6 +725,36 @@ impl<'gctx> Registry for PackageRegistry<'gctx> { ) })?; + if dep.is_opaque() { + // Currently, all opaque dependencies are builtins. + // Create a dummy Summary that can be replaced with a real package during + // unit generation + trace!( + "Injecting package to satisfy builtin dependency on {}", + dep.package_name() + ); + let ver = if dep.package_name() == "compiler_builtins" { + //TODO: hack + Version::new(0, 1, 160) + } else { + Version::new(0, 0, 0) + }; + let pkg_id = PackageId::new( + dep.package_name(), + ver, + SourceId::new_builtin(&dep.package_name()).expect("SourceId ok"), + ); + + let summary = Summary::new( + pkg_id, + vec![], + &BTreeMap::new(), + Option::::None, + None, + )?; + f(IndexSummary::Candidate(summary)); + return Poll::Ready(Ok(())); + } let source = self.sources.get_mut(dep.source_id()); match (override_summary, source) { (Some(_), None) => { diff --git a/src/cargo/core/resolver/context.rs b/src/cargo/core/resolver/context.rs index aa6f2d7e5ac..6283312f7ca 100644 --- a/src/cargo/core/resolver/context.rs +++ b/src/cargo/core/resolver/context.rs @@ -25,6 +25,10 @@ pub struct ResolverContext { /// a way to look up for a package in activations what packages required it /// and all of the exact deps that it fulfilled. pub parents: Graph>, + // Opaque dependencies require a separate resolver run as they allow for multiple + // different semver-compatible versions of crates in the final resolve. This is the + // (unactivated) set of Summaries that need handling in a future invocation + //pub promises: HashSet, } /// When backtracking it can be useful to know how far back to go. diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index eb7a581d145..6dc3fae0ad1 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -48,6 +48,9 @@ pub struct RegistryQueryer<'a, T: Registry> { >, /// all the cases we ended up using a supplied replacement used_replacements: HashMap, + /// Cached builtin dependencies that should be injected. Empty implies that builtins shouldn't + /// be injected + builtins: Vec, } impl<'a, T: Registry> RegistryQueryer<'a, T> { @@ -55,7 +58,24 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { registry: &'a mut T, replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, + inject_builtins: bool, ) -> Self { + let builtins = if inject_builtins { + [ + "std", + "alloc", + "core", + "panic_unwind", + "proc_macro", + "compiler_builtins", + ] + .iter() + .map(|&krate| Dependency::new_injected_builtin(krate.into())) + .collect() + } else { + vec![] + }; + RegistryQueryer { registry, replacements, @@ -63,6 +83,7 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { registry_cache: HashMap::new(), summary_cache: HashMap::new(), used_replacements: HashMap::new(), + builtins, } } @@ -238,10 +259,11 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { { return Ok(out.0.clone()); } + // 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, deps) = resolve_features(parent, candidate, opts, &self.builtins)?; // Next, transform all dependencies into a list of possible candidates // which can satisfy that dependency. @@ -291,10 +313,20 @@ pub fn resolve_features<'b>( parent: Option, s: &'b Summary, opts: &'b ResolveOpts, + builtins: &[Dependency], ) -> ActivateResult<(HashSet, Vec<(Dependency, FeaturesSet)>)> { // First, filter by dev-dependencies. let deps = s.dependencies(); - let deps = deps.iter().filter(|d| d.is_transitive() || opts.dev_deps); + + let deps = deps + .into_iter() + .filter(|d| d.is_transitive() || opts.dev_deps); + let builtin_deps = if s.source_id().is_builtin() { + // Don't add builtin deps to dummy builtin packages + None + } else { + Some(builtins.iter()) + }; let reqs = build_requirements(parent, s, opts)?; let mut ret = Vec::new(); @@ -302,7 +334,7 @@ pub fn resolve_features<'b>( let mut valid_dep_names = HashSet::new(); // Next, collect all actually enabled dependencies and their features. - for dep in deps { + for dep in deps.chain(builtin_deps.into_iter().flatten()) { // Skip optional dependencies, but not those enabled through a // feature if dep.is_optional() && !reqs.deps.contains_key(&dep.name_in_toml()) { diff --git a/src/cargo/core/resolver/encode.rs b/src/cargo/core/resolver/encode.rs index 4f437123282..f30746b2dc7 100644 --- a/src/cargo/core/resolver/encode.rs +++ b/src/cargo/core/resolver/encode.rs @@ -661,7 +661,8 @@ pub fn encodable_package_id( } fn encodable_source_id(id: SourceId, version: ResolveVersion) -> Option { - if id.is_path() { + // TODO: Not enough to stop builtins from appearing in the lockfile + if id.is_path() || id.is_builtin() { None } else { Some( diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 3d2be9fda4e..aef821bbdda 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -126,6 +126,7 @@ pub fn resolve( version_prefs: &VersionPreferences, resolve_version: ResolveVersion, gctx: Option<&GlobalContext>, + inject_builtins: bool, ) -> CargoResult { let first_version = match gctx { Some(config) if config.cli_unstable().direct_minimal_versions => { @@ -133,7 +134,7 @@ pub fn resolve( } _ => None, }; - let mut registry = RegistryQueryer::new(registry, replacements, version_prefs); + let mut registry = RegistryQueryer::new(registry, replacements, version_prefs, inject_builtins); // Global cache of the reasons for each time we backtrack. let mut past_conflicting_activations = conflict_cache::ConflictCache::new(); diff --git a/src/cargo/core/resolver/types.rs b/src/cargo/core/resolver/types.rs index 345b6ccbdd1..9187892e462 100644 --- a/src/cargo/core/resolver/types.rs +++ b/src/cargo/core/resolver/types.rs @@ -138,7 +138,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. diff --git a/src/cargo/core/source_id.rs b/src/cargo/core/source_id.rs index 51f566dc598..6daa3d7a70b 100644 --- a/src/cargo/core/source_id.rs +++ b/src/cargo/core/source_id.rs @@ -140,6 +140,21 @@ impl SourceId { } } + pub fn new_builtin(name: &str) -> CargoResult { + // Injecting builtins earlier (somewhere with access to RustcTargetData) is needed instead of this + let home = std::env::var("HOME").expect("HOME is set"); + let path = format!( + "file://{home}/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/" + ); + if name == "compiler_builtins" { + Self::from_url(&format!( + "builtin+{path}compiler-builtins/compiler-builtins" + )) + } else { + Self::from_url(&format!("builtin+{path}{name}")) + } + } + /// Parses a source URL and returns the corresponding ID. /// /// ## Example @@ -391,6 +406,10 @@ impl SourceId { matches!(self.inner.kind, SourceKind::Git(_)) } + pub fn is_builtin(self) -> bool { + matches!(self.inner.kind, SourceKind::Builtin) + } + /// Creates an implementation of `Source` corresponding to this ID. /// /// * `yanked_whitelist` --- Packages allowed to be used, even if they are yanked. diff --git a/src/cargo/core/summary.rs b/src/cargo/core/summary.rs index 7d9dd83fc2e..2eeeb6c88a6 100644 --- a/src/cargo/core/summary.rs +++ b/src/cargo/core/summary.rs @@ -82,6 +82,7 @@ impl Summary { ) } } + let feature_map = build_feature_map(features, &dependencies)?; Ok(Summary { inner: Arc::new(Inner { @@ -96,6 +97,10 @@ impl Summary { }) } + // Virtual summary representing a package Cargo knows how to retrieve later + // pub fn new_builtin(pkg_id: PackageId, + // features: &BTreeMap>) -> + pub fn package_id(&self) -> PackageId { self.inner.package_id } diff --git a/src/cargo/ops/cargo_compile/mod.rs b/src/cargo/ops/cargo_compile/mod.rs index 64e81fdf3a4..f6b75eb0fca 100644 --- a/src/cargo/ops/cargo_compile/mod.rs +++ b/src/cargo/ops/cargo_compile/mod.rs @@ -322,6 +322,7 @@ pub fn create_bcx<'a, 'gctx>( has_dev_units, ForceAllTargets::No, dry_run, + true, )?; let WorkspaceResolve { mut pkg_set, diff --git a/src/cargo/ops/cargo_output_metadata.rs b/src/cargo/ops/cargo_output_metadata.rs index 929a4892e97..9f94d7664f7 100644 --- a/src/cargo/ops/cargo_output_metadata.rs +++ b/src/cargo/ops/cargo_output_metadata.rs @@ -165,6 +165,7 @@ fn build_resolve_graph( HasDevUnits::Yes, force_all, dry_run, + true, )?; let package_map: BTreeMap = ws_resolve diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 63c0b139520..2f16547ff72 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -779,6 +779,7 @@ fn build_lock( None, &[], true, + true, )?; let pkg_set = ops::get_resolved_packages(&new_resolve, tmp_reg)?; diff --git a/src/cargo/ops/cargo_update.rs b/src/cargo/ops/cargo_update.rs index 080401cbe07..974d16fa980 100644 --- a/src/cargo/ops/cargo_update.rs +++ b/src/cargo/ops/cargo_update.rs @@ -47,6 +47,7 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> { None, &[], true, + true, )?; ops::write_pkg_lockfile(ws, &mut resolve)?; print_lockfile_changes(ws, previous_resolve, &resolve, &mut registry)?; @@ -87,6 +88,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes None, &[], true, + true, )? } } @@ -177,6 +179,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes Some(&keep), &[], true, + true, )?; print_lockfile_updates( diff --git a/src/cargo/ops/fix/mod.rs b/src/cargo/ops/fix/mod.rs index e7632e43e76..53d6fd479a4 100644 --- a/src/cargo/ops/fix/mod.rs +++ b/src/cargo/ops/fix/mod.rs @@ -624,6 +624,7 @@ fn check_resolver_change<'gctx>( has_dev_units, crate::core::resolver::features::ForceAllTargets::No, dry_run, + true, )?; let feature_opts = FeatureOpts::new_behavior(ResolveBehavior::V2, has_dev_units); diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index 661dc05f1c0..9715d7a11c0 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -133,7 +133,7 @@ version. This may also occur with an optional dependency that is not enabled."; /// `package`, which don't specify any options or features. pub fn resolve_ws<'a>(ws: &Workspace<'a>, dry_run: bool) -> CargoResult<(PackageSet<'a>, Resolve)> { let mut registry = ws.package_registry()?; - let resolve = resolve_with_registry(ws, &mut registry, dry_run)?; + let resolve = resolve_with_registry(ws, &mut registry, dry_run, true)?; let packages = get_resolved_packages(&resolve, registry)?; Ok((packages, resolve)) } @@ -157,6 +157,7 @@ pub fn resolve_ws_with_opts<'gctx>( has_dev_units: HasDevUnits, force_all_targets: ForceAllTargets, dry_run: bool, + inject_builtins: bool, ) -> CargoResult> { let feature_unification = ws.resolve_feature_unification(); let individual_specs = match feature_unification { @@ -186,13 +187,14 @@ pub fn resolve_ws_with_opts<'gctx>( None, specs, add_patches, + inject_builtins, )?; ops::print_lockfile_changes(ws, None, &resolved_with_overrides, &mut registry)?; (resolve, resolved_with_overrides) } else if ws.require_optional_deps() { // First, resolve the root_package's *listed* dependencies, as well as // downloading and updating all remotes and such. - let resolve = resolve_with_registry(ws, &mut registry, dry_run)?; + let resolve = resolve_with_registry(ws, &mut registry, dry_run, inject_builtins)?; // No need to add patches again, `resolve_with_registry` has done it. let add_patches = false; @@ -244,6 +246,7 @@ pub fn resolve_ws_with_opts<'gctx>( None, specs, add_patches, + inject_builtins, )?; (Some(resolve), resolved_with_overrides) } else { @@ -258,6 +261,7 @@ pub fn resolve_ws_with_opts<'gctx>( None, specs, add_patches, + inject_builtins, )?; // Skipping `print_lockfile_changes` as there are cases where this prints irrelevant // information @@ -352,6 +356,7 @@ fn resolve_with_registry<'gctx>( ws: &Workspace<'gctx>, registry: &mut PackageRegistry<'gctx>, dry_run: bool, + inject_builtins: bool, ) -> CargoResult { let prev = ops::load_pkg_lockfile(ws)?; let mut resolve = resolve_with_previous( @@ -363,6 +368,7 @@ fn resolve_with_registry<'gctx>( None, &[], true, + inject_builtins, )?; let print = if !ws.is_ephemeral() && ws.require_optional_deps() { @@ -410,6 +416,7 @@ pub fn resolve_with_previous<'gctx>( keep_previous: Option>, specs: &[PackageIdSpec], register_patches: bool, + inject_builtins: bool, ) -> CargoResult { // We only want one Cargo at a time resolving a crate graph since this can // involve a lot of frobbing of the global caches. @@ -509,6 +516,7 @@ pub fn resolve_with_previous<'gctx>( &version_prefs, ResolveVersion::with_rust_version(ws.lowest_rust_version()), Some(ws.gctx()), + inject_builtins, )?; let patches = registry.patches().values().flat_map(|v| v.iter()); diff --git a/src/cargo/ops/tree/mod.rs b/src/cargo/ops/tree/mod.rs index 4344daa49e7..65f044fc71a 100644 --- a/src/cargo/ops/tree/mod.rs +++ b/src/cargo/ops/tree/mod.rs @@ -173,6 +173,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() has_dev, force_all, dry_run, + true, )?; let package_map: HashMap = ws_resolve diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index c32fb25fabb..31f3648ecf9 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -1422,6 +1422,7 @@ fn get_packages() -> CargoResult> { has_dev_units, force_all_targets, dry_run, + true, )?; let packages = ws_resolve From 05903b1de7b9e329f98a67cdf48d4ca85dad9352 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 25 Feb 2026 14:35:32 +0000 Subject: [PATCH 3/7] Unit generation changes for opaque dependencies --- src/cargo/core/compiler/unit_dependencies.rs | 90 ++++++++++---------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index 51ea3795ad8..e379647b9d9 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -52,6 +52,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. @@ -92,7 +94,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, @@ -119,6 +121,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, @@ -129,15 +132,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 @@ -188,36 +190,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, - })); - 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); } } } @@ -334,16 +314,38 @@ fn compute_deps( )?; ret.push(unit_dep); } else { - let unit_dep = new_unit_dep( - state, - unit, - dep_pkg, - dep_lib, - 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() { + let unit: Vec<_> = state.opaque_roots[&unit.kind.for_target(dep_lib)] + .iter() + .filter(|&u| u.pkg.name() == dep_pkg_id.name()) + .collect(); + assert!( + unit.len() == 1, + "libstd was resolved with all possible builtin deps as roots" + ); + let unit = unit[0]; + 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, + } + } else { + new_unit_dep( + state, + unit, + dep_pkg, + dep_lib, + dep_unit_for, + unit.kind.for_target(dep_lib), + mode, + IS_NO_ARTIFACT_DEP, + )? + }; ret.push(unit_dep); } From 7948ac06cb92fffe98d5990fa3b03661974b8603 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Wed, 25 Feb 2026 14:10:55 +0000 Subject: [PATCH 4/7] Allow overriding src root for build-std tests --- src/cargo/core/compiler/standard_lib.rs | 2 +- src/cargo/core/dependency.rs | 4 ++-- src/cargo/core/registry.rs | 6 +----- src/cargo/core/resolver/dep_cache.rs | 7 ++++--- src/cargo/core/resolver/mod.rs | 5 +++-- src/cargo/core/source_id.rs | 21 +++++++++------------ src/cargo/ops/cargo_package/mod.rs | 2 +- src/cargo/ops/cargo_update.rs | 6 +++--- src/cargo/ops/resolve.rs | 25 ++++++++++++++++--------- 9 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index d47400ae95b..38eea4b484e 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -217,7 +217,7 @@ fn generate_roots( Ok(()) } -fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult { +pub 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/cargo/core/dependency.rs b/src/cargo/core/dependency.rs index 5806aa6d3b2..23292a683a2 100644 --- a/src/cargo/core/dependency.rs +++ b/src/cargo/core/dependency.rs @@ -171,12 +171,12 @@ impl Dependency { } } - pub fn new_injected_builtin(name: InternedString) -> Dependency { + pub fn new_injected_builtin(name: InternedString, root: &PathBuf) -> Dependency { assert!(!name.is_empty()); Dependency { inner: Arc::new(Inner { name, - source_id: SourceId::new_builtin(&name).expect("package name is valid url"), + source_id: SourceId::new_builtin(&name, root).expect("package name is valid url"), registry_id: None, req: OptVersionReq::Any, kind: DepKind::Normal, diff --git a/src/cargo/core/registry.rs b/src/cargo/core/registry.rs index dcd12673903..eea5049fe59 100644 --- a/src/cargo/core/registry.rs +++ b/src/cargo/core/registry.rs @@ -739,11 +739,7 @@ impl<'gctx> Registry for PackageRegistry<'gctx> { } else { Version::new(0, 0, 0) }; - let pkg_id = PackageId::new( - dep.package_name(), - ver, - SourceId::new_builtin(&dep.package_name()).expect("SourceId ok"), - ); + let pkg_id = PackageId::new(dep.package_name(), ver, dep.source_id()); let summary = Summary::new( pkg_id, diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index 6dc3fae0ad1..d31647e6edb 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -27,6 +27,7 @@ use crate::util::interning::{INTERNED_DEFAULT, InternedString}; use anyhow::Context as _; use std::collections::{BTreeSet, HashMap, HashSet}; use std::fmt::Write; +use std::path::PathBuf; use std::rc::Rc; use std::task::Poll; use tracing::debug; @@ -58,9 +59,9 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { registry: &'a mut T, replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, - inject_builtins: bool, + builtins_root: Option<&PathBuf>, ) -> Self { - let builtins = if inject_builtins { + let builtins = if let Some(root) = builtins_root { [ "std", "alloc", @@ -70,7 +71,7 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { "compiler_builtins", ] .iter() - .map(|&krate| Dependency::new_injected_builtin(krate.into())) + .map(|&krate| Dependency::new_injected_builtin(krate.into(), root)) .collect() } else { vec![] diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index aef821bbdda..3279bd8f33e 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -59,6 +59,7 @@ //! over the place. use std::collections::{BTreeMap, HashMap, HashSet}; +use std::path::PathBuf; use std::rc::Rc; use std::time::{Duration, Instant}; @@ -126,7 +127,7 @@ pub fn resolve( version_prefs: &VersionPreferences, resolve_version: ResolveVersion, gctx: Option<&GlobalContext>, - inject_builtins: bool, + builtins_root: Option<&PathBuf>, ) -> CargoResult { let first_version = match gctx { Some(config) if config.cli_unstable().direct_minimal_versions => { @@ -134,7 +135,7 @@ pub fn resolve( } _ => None, }; - let mut registry = RegistryQueryer::new(registry, replacements, version_prefs, inject_builtins); + let mut registry = RegistryQueryer::new(registry, replacements, version_prefs, builtins_root); // Global cache of the reasons for each time we backtrack. let mut past_conflicting_activations = conflict_cache::ConflictCache::new(); diff --git a/src/cargo/core/source_id.rs b/src/cargo/core/source_id.rs index 6daa3d7a70b..85165ccd1de 100644 --- a/src/cargo/core/source_id.rs +++ b/src/cargo/core/source_id.rs @@ -140,19 +140,16 @@ impl SourceId { } } - pub fn new_builtin(name: &str) -> CargoResult { - // Injecting builtins earlier (somewhere with access to RustcTargetData) is needed instead of this - let home = std::env::var("HOME").expect("HOME is set"); - let path = format!( - "file://{home}/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/" - ); - if name == "compiler_builtins" { - Self::from_url(&format!( - "builtin+{path}compiler-builtins/compiler-builtins" - )) + pub fn new_builtin(name: &str, root: &PathBuf) -> CargoResult { + let path = if name == "compiler_builtins" { + root.join("compiler-builtins").join("compiler-builtins") } else { - Self::from_url(&format!("builtin+{path}{name}")) - } + root.join(name) + }; + Self::from_url(&format!( + "builtin+file://{}", + path.to_str().expect("path is utf8") + )) } /// Parses a source URL and returns the corresponding ID. diff --git a/src/cargo/ops/cargo_package/mod.rs b/src/cargo/ops/cargo_package/mod.rs index 2f16547ff72..f4de741dbe6 100644 --- a/src/cargo/ops/cargo_package/mod.rs +++ b/src/cargo/ops/cargo_package/mod.rs @@ -779,7 +779,7 @@ fn build_lock( None, &[], true, - true, + None, )?; let pkg_set = ops::get_resolved_packages(&new_resolve, tmp_reg)?; diff --git a/src/cargo/ops/cargo_update.rs b/src/cargo/ops/cargo_update.rs index 974d16fa980..b9571693d57 100644 --- a/src/cargo/ops/cargo_update.rs +++ b/src/cargo/ops/cargo_update.rs @@ -47,7 +47,7 @@ pub fn generate_lockfile(ws: &Workspace<'_>) -> CargoResult<()> { None, &[], true, - true, + None, )?; ops::write_pkg_lockfile(ws, &mut resolve)?; print_lockfile_changes(ws, previous_resolve, &resolve, &mut registry)?; @@ -88,7 +88,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes None, &[], true, - true, + None, )? } } @@ -179,7 +179,7 @@ pub fn update_lockfile(ws: &Workspace<'_>, opts: &UpdateOptions<'_>) -> CargoRes Some(&keep), &[], true, - true, + None, )?; print_lockfile_updates( diff --git a/src/cargo/ops/resolve.rs b/src/cargo/ops/resolve.rs index 9715d7a11c0..3ddecade1f9 100644 --- a/src/cargo/ops/resolve.rs +++ b/src/cargo/ops/resolve.rs @@ -63,6 +63,7 @@ use crate::core::PackageIdSpecQuery; use crate::core::PackageSet; use crate::core::SourceId; use crate::core::Workspace; +use crate::core::compiler::standard_lib::detect_sysroot_src_path; use crate::core::compiler::{CompileKind, RustcTargetData}; use crate::core::registry::{LockedPatchDependency, PackageRegistry}; use crate::core::resolver::features::{ @@ -85,6 +86,7 @@ use cargo_util::paths; use cargo_util_schemas::core::PartialVersion; use std::borrow::Cow; use std::collections::{HashMap, HashSet}; +use std::path::PathBuf; use std::rc::Rc; use tracing::{debug, trace}; @@ -133,7 +135,9 @@ version. This may also occur with an optional dependency that is not enabled."; /// `package`, which don't specify any options or features. pub fn resolve_ws<'a>(ws: &Workspace<'a>, dry_run: bool) -> CargoResult<(PackageSet<'a>, Resolve)> { let mut registry = ws.package_registry()?; - let resolve = resolve_with_registry(ws, &mut registry, dry_run, true)?; + let target_data = RustcTargetData::new(ws, &[])?; + let builtins_root = detect_sysroot_src_path(&target_data)?; + let resolve = resolve_with_registry(ws, &mut registry, dry_run, Some(&builtins_root))?; let packages = get_resolved_packages(&resolve, registry)?; Ok((packages, resolve)) } @@ -174,6 +178,9 @@ pub fn resolve_ws_with_opts<'gctx>( .cloned() .collect(); let specs = &specs[..]; + let builtins_root = + inject_builtins.then(|| detect_sysroot_src_path(target_data).expect("sysroot path ok")); + let builtins_root = builtins_root.as_ref(); let mut registry = ws.package_registry()?; let (resolve, resolved_with_overrides) = if ws.ignore_lock() { let add_patches = true; @@ -187,14 +194,14 @@ pub fn resolve_ws_with_opts<'gctx>( None, specs, add_patches, - inject_builtins, + builtins_root, )?; ops::print_lockfile_changes(ws, None, &resolved_with_overrides, &mut registry)?; (resolve, resolved_with_overrides) } else if ws.require_optional_deps() { // First, resolve the root_package's *listed* dependencies, as well as // downloading and updating all remotes and such. - let resolve = resolve_with_registry(ws, &mut registry, dry_run, inject_builtins)?; + let resolve = resolve_with_registry(ws, &mut registry, dry_run, builtins_root)?; // No need to add patches again, `resolve_with_registry` has done it. let add_patches = false; @@ -246,7 +253,7 @@ pub fn resolve_ws_with_opts<'gctx>( None, specs, add_patches, - inject_builtins, + builtins_root, )?; (Some(resolve), resolved_with_overrides) } else { @@ -261,7 +268,7 @@ pub fn resolve_ws_with_opts<'gctx>( None, specs, add_patches, - inject_builtins, + builtins_root, )?; // Skipping `print_lockfile_changes` as there are cases where this prints irrelevant // information @@ -356,7 +363,7 @@ fn resolve_with_registry<'gctx>( ws: &Workspace<'gctx>, registry: &mut PackageRegistry<'gctx>, dry_run: bool, - inject_builtins: bool, + builtins_root: Option<&PathBuf>, ) -> CargoResult { let prev = ops::load_pkg_lockfile(ws)?; let mut resolve = resolve_with_previous( @@ -368,7 +375,7 @@ fn resolve_with_registry<'gctx>( None, &[], true, - inject_builtins, + builtins_root, )?; let print = if !ws.is_ephemeral() && ws.require_optional_deps() { @@ -416,7 +423,7 @@ pub fn resolve_with_previous<'gctx>( keep_previous: Option>, specs: &[PackageIdSpec], register_patches: bool, - inject_builtins: bool, + builtins_root: Option<&PathBuf>, ) -> CargoResult { // We only want one Cargo at a time resolving a crate graph since this can // involve a lot of frobbing of the global caches. @@ -516,7 +523,7 @@ pub fn resolve_with_previous<'gctx>( &version_prefs, ResolveVersion::with_rust_version(ws.lowest_rust_version()), Some(ws.gctx()), - inject_builtins, + builtins_root, )?; let patches = registry.patches().values().flat_map(|v| v.iter()); From a8904908c9346853a3437352b69cbd45a19aa2d0 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Thu, 26 Feb 2026 15:20:12 +0000 Subject: [PATCH 5/7] Inject test Unit --- src/cargo/core/compiler/unit_dependencies.rs | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index e379647b9d9..d315f8bfd9f 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -361,6 +361,28 @@ fn compute_deps( } state.dev_dependency_edges.extend(dev_deps); + if unit.mode.is_rustc_test() && unit.target.harness() { + let unit: Vec<_> = state.opaque_roots[&unit.kind] + .iter() + .filter(|&u| u.pkg.name() == "test") + .collect(); + assert!( + unit.len() == 1, + "libstd was resolved with test crate as root" + ); + let unit = unit[0]; + let unitdep = 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, + }; + 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. From be19f77008c0b2bbbdf9c5cb407b8ad3705ec19e Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Thu, 19 Mar 2026 12:27:52 +0000 Subject: [PATCH 6/7] Enable injecting deps depending on -Zbuild-std args Not a great solution and will need reworking, but good enough to unblock some tests for now --- src/cargo/core/compiler/standard_lib.rs | 6 +++++- src/cargo/core/resolver/dep_cache.rs | 26 ++++++++++++------------- src/cargo/core/resolver/mod.rs | 3 ++- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index 38eea4b484e..563475933d7 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -15,7 +15,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. diff --git a/src/cargo/core/resolver/dep_cache.rs b/src/cargo/core/resolver/dep_cache.rs index d31647e6edb..0ceaa4459c0 100644 --- a/src/cargo/core/resolver/dep_cache.rs +++ b/src/cargo/core/resolver/dep_cache.rs @@ -9,6 +9,8 @@ //! //! This module impl that cache in all the gory details +use crate::GlobalContext; +use crate::core::compiler::standard_lib::std_crates; use crate::core::resolver::context::ResolverContext; use crate::core::resolver::errors::describe_path_in_context; use crate::core::resolver::types::{ConflictReason, DepInfo, FeaturesSet}; @@ -60,21 +62,17 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, builtins_root: Option<&PathBuf>, + gctx: Option<&GlobalContext>, ) -> Self { - let builtins = if let Some(root) = builtins_root { - [ - "std", - "alloc", - "core", - "panic_unwind", - "proc_macro", - "compiler_builtins", - ] - .iter() - .map(|&krate| Dependency::new_injected_builtin(krate.into(), root)) - .collect() - } else { - vec![] + // TODO: Hack - default should come from TargetInfo + // units is fine empty as we don't need to inject test here, but it looks weird. + let buildstd_crates = gctx.and_then(|gctx| gctx.cli_unstable().build_std.as_ref()); + let builtins = match (builtins_root, buildstd_crates) { + (Some(root), Some(crates)) => std_crates(&crates, "std", &[]) + .iter() + .map(|&krate| Dependency::new_injected_builtin(krate.into(), root)) + .collect(), + (_, _) => vec![], }; RegistryQueryer { diff --git a/src/cargo/core/resolver/mod.rs b/src/cargo/core/resolver/mod.rs index 3279bd8f33e..8cd7f8ffb58 100644 --- a/src/cargo/core/resolver/mod.rs +++ b/src/cargo/core/resolver/mod.rs @@ -135,7 +135,8 @@ pub fn resolve( } _ => None, }; - let mut registry = RegistryQueryer::new(registry, replacements, version_prefs, builtins_root); + let mut registry = + RegistryQueryer::new(registry, replacements, version_prefs, builtins_root, gctx); // Global cache of the reasons for each time we backtrack. let mut past_conflicting_activations = conflict_cache::ConflictCache::new(); From ab7d8b02a58315a031dfff95e2d07545b9880736 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Thu, 19 Mar 2026 12:32:46 +0000 Subject: [PATCH 7/7] Move compiler-builtins in mock-std to match the real one --- .../compiler-builtins}/Cargo.toml | 0 .../compiler-builtins}/build.rs | 0 .../compiler-builtins}/src/lib.rs | 0 tests/testsuite/mock-std/library/test/Cargo.toml | 2 +- 4 files changed, 1 insertion(+), 1 deletion(-) rename tests/testsuite/mock-std/library/{compiler_builtins => compiler-builtins/compiler-builtins}/Cargo.toml (100%) rename tests/testsuite/mock-std/library/{compiler_builtins => compiler-builtins/compiler-builtins}/build.rs (100%) rename tests/testsuite/mock-std/library/{compiler_builtins => compiler-builtins/compiler-builtins}/src/lib.rs (100%) diff --git a/tests/testsuite/mock-std/library/compiler_builtins/Cargo.toml b/tests/testsuite/mock-std/library/compiler-builtins/compiler-builtins/Cargo.toml similarity index 100% rename from tests/testsuite/mock-std/library/compiler_builtins/Cargo.toml rename to tests/testsuite/mock-std/library/compiler-builtins/compiler-builtins/Cargo.toml diff --git a/tests/testsuite/mock-std/library/compiler_builtins/build.rs b/tests/testsuite/mock-std/library/compiler-builtins/compiler-builtins/build.rs similarity index 100% rename from tests/testsuite/mock-std/library/compiler_builtins/build.rs rename to tests/testsuite/mock-std/library/compiler-builtins/compiler-builtins/build.rs diff --git a/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs b/tests/testsuite/mock-std/library/compiler-builtins/compiler-builtins/src/lib.rs similarity index 100% rename from tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs rename to tests/testsuite/mock-std/library/compiler-builtins/compiler-builtins/src/lib.rs diff --git a/tests/testsuite/mock-std/library/test/Cargo.toml b/tests/testsuite/mock-std/library/test/Cargo.toml index b9f51eda704..fbbeda10051 100644 --- a/tests/testsuite/mock-std/library/test/Cargo.toml +++ b/tests/testsuite/mock-std/library/test/Cargo.toml @@ -7,5 +7,5 @@ edition = "2018" [dependencies] std = { path = "../std" } panic_unwind = { path = "../panic_unwind" } -compiler_builtins = { path = "../compiler_builtins" } +compiler_builtins = { path = "../compiler-builtins/compiler-builtins" } registry-dep-using-std = { version = "*", features = ['mockbuild'] }