diff --git a/benches/benchsuite/benches/resolve.rs b/benches/benchsuite/benches/resolve.rs index b41c39ba0fe..1b7e50bd849 100644 --- a/benches/benchsuite/benches/resolve.rs +++ b/benches/benchsuite/benches/resolve.rs @@ -25,7 +25,7 @@ struct ResolveInfo<'gctx> { fn do_resolve<'gctx>(gctx: &'gctx GlobalContext, ws_root: &Path) -> ResolveInfo<'gctx> { let requested_kinds = [CompileKind::Host]; let ws = Workspace::new(&ws_root.join("Cargo.toml"), gctx).unwrap(); - let mut target_data = RustcTargetData::new(&ws, &requested_kinds).unwrap(); + let mut target_data = RustcTargetData::new(gctx, Some(&ws), &requested_kinds).unwrap(); let cli_features = CliFeatures::from_command_line(&[], false, true).unwrap(); let pkgs = cargo::ops::Packages::Default; let specs = pkgs.to_package_id_specs(&ws).unwrap(); diff --git a/src/compiler/build_context/target_info.rs b/src/compiler/build_context/target_info.rs index 4f5daab4afb..e13e262d4f0 100644 --- a/src/compiler/build_context/target_info.rs +++ b/src/compiler/build_context/target_info.rs @@ -989,11 +989,11 @@ pub struct RustcTargetData<'gctx> { impl<'gctx> RustcTargetData<'gctx> { #[tracing::instrument(skip_all)] pub fn new( - ws: &Workspace<'gctx>, + gctx: &'gctx GlobalContext, + ws: Option<&Workspace<'gctx>>, requested_kinds: &[CompileKind], ) -> CargoResult> { - let gctx = ws.gctx(); - let rustc = gctx.load_global_rustc(Some(ws))?; + let rustc = gctx.load_global_rustc(ws)?; let mut target_config = HashMap::default(); let mut target_info = HashMap::default(); let target_applies_to_host = gctx.target_applies_to_host()?; @@ -1054,16 +1054,14 @@ impl<'gctx> RustcTargetData<'gctx> { .iter() .filter_map(|d| d.artifact()?.target()?.to_compile_kind()) } - let all_kinds = requested_kinds - .iter() - .copied() - .chain(ws.members().flat_map(|p| { - p.manifest() - .default_kind() - .into_iter() - .chain(p.manifest().forced_kind()) - .chain(artifact_targets(p)) - })); + let members = ws.into_iter().flat_map(|ws| ws.members()); + let all_kinds = requested_kinds.iter().copied().chain(members.flat_map(|p| { + p.manifest() + .default_kind() + .into_iter() + .chain(p.manifest().forced_kind()) + .chain(artifact_targets(p)) + })); for kind in all_kinds { res.merge_compile_kind(kind)?; } diff --git a/src/compiler/standard_lib.rs b/src/compiler/standard_lib.rs index 319e9a9fab0..b398c741ff4 100644 --- a/src/compiler/standard_lib.rs +++ b/src/compiler/standard_lib.rs @@ -218,7 +218,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/compiler/unit_dependencies.rs b/src/compiler/unit_dependencies.rs index be8e9a4c7b9..1e7833e9228 100644 --- a/src/compiler/unit_dependencies.rs +++ b/src/compiler/unit_dependencies.rs @@ -1138,6 +1138,7 @@ impl<'a, 'gctx> State<'a, 'gctx> { let kind = unit.kind; self.resolve() .deps(pkg_id) + .filter(|(id, _)| !id.source_id().is_builtin()) .filter_map(|(id, deps)| { assert!(!deps.is_empty()); let deps: Vec<_> = deps diff --git a/src/ops/cargo_clean.rs b/src/ops/cargo_clean.rs index 6647be50b25..36ca8f86122 100644 --- a/src/ops/cargo_clean.rs +++ b/src/ops/cargo_clean.rs @@ -195,7 +195,7 @@ fn clean_specs( ) -> CargoResult<()> { // Clean specific packages. let requested_kinds = CompileKind::from_requested_targets(clean_ctx.gctx, targets)?; - let target_data = RustcTargetData::new(ws, &requested_kinds)?; + let target_data = RustcTargetData::new(ws.gctx(), Some(ws), &requested_kinds)?; let (pkg_set, resolve) = ops::resolve_ws(ws, dry_run)?; let prof_dir_name = profiles.get_dir_name(); let host_layout = Layout::new(ws, None, &prof_dir_name, true, true)?; diff --git a/src/ops/cargo_compile/mod.rs b/src/ops/cargo_compile/mod.rs index bf55ba1858c..a0847dd96ec 100644 --- a/src/ops/cargo_compile/mod.rs +++ b/src/ops/cargo_compile/mod.rs @@ -294,7 +294,7 @@ pub fn create_bcx<'a, 'gctx>( } gctx.validate_term_config()?; - let mut target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?; + let mut target_data = RustcTargetData::new(gctx, Some(ws), &build_config.requested_kinds)?; let specs = spec.to_package_id_specs(ws)?; let has_dev_units = { diff --git a/src/ops/cargo_fetch.rs b/src/ops/cargo_fetch.rs index 731a3775a20..d25e2444d99 100644 --- a/src/ops/cargo_fetch.rs +++ b/src/ops/cargo_fetch.rs @@ -34,7 +34,7 @@ pub fn fetch<'a>( let gctx = ws.gctx(); let build_config = BuildConfig::new(gctx, jobs, keep_going, &options.targets, UserIntent::Build)?; - let mut data = RustcTargetData::new(ws, &build_config.requested_kinds)?; + let mut data = RustcTargetData::new(ws.gctx(), Some(ws), &build_config.requested_kinds)?; let mut fetched_packages = HashSet::default(); let mut deps_to_fetch = ws.members().map(|p| p.package_id()).collect::>(); let mut to_download = Vec::new(); diff --git a/src/ops/cargo_fix/mod.rs b/src/ops/cargo_fix/mod.rs index 90aeb7a4b21..940ad943041 100644 --- a/src/ops/cargo_fix/mod.rs +++ b/src/ops/cargo_fix/mod.rs @@ -150,8 +150,11 @@ pub fn fix( ) -> CargoResult<()> { check_version_control(gctx, opts)?; - let mut target_data = - RustcTargetData::new(original_ws, &opts.compile_opts.build_config.requested_kinds)?; + let mut target_data = RustcTargetData::new( + gctx, + Some(original_ws), + &opts.compile_opts.build_config.requested_kinds, + )?; let specs = opts.compile_opts.spec.to_package_id_specs(&original_ws)?; let members: Vec<&Package> = original_ws diff --git a/src/ops/cargo_metadata.rs b/src/ops/cargo_metadata.rs index b283e5dd604..a6f91756ddf 100644 --- a/src/ops/cargo_metadata.rs +++ b/src/ops/cargo_metadata.rs @@ -144,7 +144,7 @@ fn build_resolve_graph( &metadata_opts.filter_platforms, CompileKindFallback::JustHost, )?; - let mut target_data = RustcTargetData::new(ws, &requested_kinds)?; + let mut target_data = RustcTargetData::new(ws.gctx(), Some(ws), &requested_kinds)?; // Resolve entire workspace. let specs = Packages::All(Vec::new()).to_package_id_specs(ws)?; let force_all = if metadata_opts.filter_platforms.is_empty() { diff --git a/src/ops/cargo_tree/mod.rs b/src/ops/cargo_tree/mod.rs index 95e1110a3a2..76d32cebcc4 100644 --- a/src/ops/cargo_tree/mod.rs +++ b/src/ops/cargo_tree/mod.rs @@ -148,7 +148,7 @@ pub fn build_and_print(ws: &Workspace<'_>, opts: &TreeOptions) -> CargoResult<() // TODO: Target::All is broken with -Zfeatures=itarget. To handle that properly, // `FeatureResolver` will need to be taught what "all" means. let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &requested_targets)?; - let mut target_data = RustcTargetData::new(ws, &requested_kinds)?; + let mut target_data = RustcTargetData::new(ws.gctx(), Some(ws), &requested_kinds)?; let specs = opts.packages.to_package_id_specs(ws)?; let has_dev = if opts .edge_kinds diff --git a/src/resolver/encode.rs b/src/resolver/encode.rs index 3bc4d0921af..2106db70e40 100644 --- a/src/resolver/encode.rs +++ b/src/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() { + // Lockfile support for builtins isn't yet implemented. + if id.is_path() || id.is_builtin() { None } else { Some( diff --git a/src/sources/builtin.rs b/src/sources/builtin.rs new file mode 100644 index 00000000000..697db9c2944 --- /dev/null +++ b/src/sources/builtin.rs @@ -0,0 +1,128 @@ +use crate::{ + CargoResult, GlobalContext, + compiler::{CompileKind, RustcTargetData, standard_lib::detect_sysroot_src_path}, + sources::{ + IndexSummary, RecursivePathSource, + source::{MaybePackage, QueryKind, Source}, + }, + workspace::{Dependency, Package, PackageId, SourceId}, +}; + +/// A builtin source represents standard library packages used in build-std, which are "built into" +/// the toolchain. +/// +/// It's a thin wrapper around a [`RecursivePathSource`] located in the sysroot's library source +/// path. +pub struct BuiltinSource<'gctx> { + /// The unique identifier for this source + source_id: SourceId, + /// The underlying path source which discovers packages + path_source: RecursivePathSource<'gctx>, +} + +impl<'gctx> BuiltinSource<'gctx> { + pub fn new(source_id: SourceId, gctx: &'gctx GlobalContext) -> CargoResult { + let target_data = RustcTargetData::new(gctx, None, &[CompileKind::Host])?; + let path = detect_sysroot_src_path(&target_data)?; + let path_source = RecursivePathSource::new(&path, source_id, gctx); + Ok(Self { + source_id, + path_source, + }) + } +} + +#[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, f).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) { + self.path_source.invalidate_cache(); + } + + fn set_quiet(&mut self, quiet: bool) { + self.path_source.set_quiet(quiet); + } +} + +#[cfg(test)] +mod test { + use std::path::Path; + + use crate::{ + GlobalContext, + sources::IndexSummary, + util::{VersionReqMatchMode, data_structures::HashMap}, + workspace::{Dependency, SourceId}, + }; + + #[test] + fn builtin_source() { + let mut gctx = GlobalContext::default().unwrap(); + let mock_std_root = + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); + let env = HashMap::from_iter([( + "__CARGO_TESTS_ONLY_SRC_ROOT".to_string(), + mock_std_root.display().to_string(), + )]); + gctx.set_env(env); + + let source_id = SourceId::for_builtin().unwrap(); + let dep = Dependency::new_override("core".into(), source_id); + + 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, VersionReqMatchMode::Default)); + assert!(s.dependencies().is_empty()); + assert!(s.source_id().is_builtin()) + } else { + panic!("no candidate found"); + }; + } +} 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 4c53888f8b1..d92c7984471 100644 --- a/src/sources/path.rs +++ b/src/sources/path.rs @@ -1189,12 +1189,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/util/command_prelude.rs b/src/util/command_prelude.rs index ddd2dbc73f5..c3a4f902fc1 100644 --- a/src/util/command_prelude.rs +++ b/src/util/command_prelude.rs @@ -1416,7 +1416,7 @@ fn get_packages() -> CargoResult> { let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &[])?; - let mut target_data = RustcTargetData::new(&ws, &requested_kinds)?; + let mut target_data = RustcTargetData::new(&gctx, Some(&ws), &requested_kinds)?; // `cli_features.all_features` must be true in case that `specs` is empty. let cli_features = CliFeatures::new_all(true); let has_dev_units = HasDevUnits::Yes; diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index 137f1ff7309..27e0efab1c2 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; @@ -395,6 +397,11 @@ impl SourceId { matches!(self.inner.kind, SourceKind::Git(_)) } + /// Returns `true` if this source is builtin. + pub fn is_builtin(self) -> bool { + matches!(self.inner.kind, SourceKind::Builtin) + } + /// Creates an implementation of `Source` corresponding to this ID. pub fn load<'a>(self, gctx: &'a GlobalContext) -> CargoResult> { trace!("loading SourceId; {}", self); @@ -411,7 +418,7 @@ impl SourceId { } Ok(Box::new(PathSource::new(&path, self, gctx))) } - SourceKind::Builtin => todo!("builtin source"), + SourceKind::Builtin => Ok(Box::new(BuiltinSource::new(self, gctx)?)), SourceKind::Registry | SourceKind::SparseRegistry => { Ok(Box::new(RegistrySource::remote(self, gctx)?)) } diff --git a/tests/testsuite/builtin_dep.rs b/tests/testsuite/builtin_dep.rs index 806511263f0..aba27b2f198 100644 --- a/tests/testsuite/builtin_dep.rs +++ b/tests/testsuite/builtin_dep.rs @@ -1,10 +1,12 @@ +use std::path::Path; + use crate::prelude::*; use cargo_test_support::{project, str}; #[cargo_test] fn builtin_dep_accepted() { let p = project() - .file("src/lib.rs", "use core;") + .file("src/lib.rs", "#![no_std]") .file( "Cargo.toml", r#" @@ -21,14 +23,14 @@ fn builtin_dep_accepted() { ) .build(); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); p.cargo("check") .masquerade_as_nightly_cargo(&["builtin-dependencies"]) - .with_status(101) + .env("__CARGO_TESTS_ONLY_SRC_ROOT", &root) .with_stderr_data(str![[r#" - -thread [..] panicked at [..] -not yet implemented: builtin source -[NOTE] run with `RUST_BACKTRACE=1` environment variable to display a backtrace +[LOCKING] 1 package to highest compatible version +[CHECKING] foo v0.1.0 ([ROOT]/foo) +[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s "#]]) .run(); @@ -70,6 +72,182 @@ Caused by: .run(); } +#[cargo_test] +fn resolve_contents() { + crate::standard_lib::publish_mock_std_registry_packages(); + + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + cargo-features = ["builtin-dependencies"] + + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + + [dependencies] + core = { builtin = true } + "#, + ) + .build(); + + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); + p.cargo("metadata") + .masquerade_as_nightly_cargo(&["builtin-dependencies"]) + .env("__CARGO_TESTS_ONLY_SRC_ROOT", &root) + .with_stdout_data( + str![[r#" +{ + "build_directory": "[ROOT]/foo/target", + "metadata": null, + "packages": [ + { + "authors": [ + "Alex Crichton " + ], + "categories": [], + "default_run": null, + "dependencies": [], + "description": null, + "documentation": null, + "edition": "2018", + "features": {}, + "homepage": null, + "id": "builtin://.#core", + "keywords": [], + "license": null, + "license_file": null, + "links": null, + "manifest_path": "[..]/tests/testsuite/mock-std/library/core/Cargo.toml", + "metadata": null, + "name": "core", + "publish": null, + "readme": null, + "repository": null, + "rust_version": null, + "source": "builtin://.", + "targets": [ + { + "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 + } + ], + "version": "0.0.0" + }, + { + "authors": [], + "categories": [], + "default_run": null, + "dependencies": [ + { + "features": [], + "kind": null, + "name": "core", + "optional": false, + "registry": null, + "rename": null, + "req": "*", + "source": "builtin://.", + "target": null, + "uses_default_features": true + } + ], + "description": null, + "documentation": null, + "edition": "2021", + "features": {}, + "homepage": null, + "id": "path+[ROOTURL]/foo#0.1.0", + "keywords": [], + "license": null, + "license_file": null, + "links": null, + "manifest_path": "[ROOT]/foo/Cargo.toml", + "metadata": null, + "name": "foo", + "publish": null, + "readme": null, + "repository": null, + "rust_version": null, + "source": null, + "targets": [ + { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2021", + "kind": [ + "lib" + ], + "name": "foo", + "src_path": "[ROOT]/foo/src/lib.rs", + "test": true + } + ], + "version": "0.1.0" + } + ], + "resolve": { + "nodes": [ + { + "dependencies": [], + "deps": [], + "features": [], + "id": "builtin://.#core" + }, + { + "dependencies": [ + "builtin://.#core" + ], + "deps": [ + { + "dep_kinds": [ + { + "kind": null, + "target": null + } + ], + "name": "core", + "pkg": "builtin://.#core" + } + ], + "features": [], + "id": "path+[ROOTURL]/foo#0.1.0" + } + ], + "root": "path+[ROOTURL]/foo#0.1.0" + }, + "target_directory": "[ROOT]/foo/target", + "version": 1, + "workspace_default_members": [ + "path+[ROOTURL]/foo#0.1.0" + ], + "workspace_members": [ + "path+[ROOTURL]/foo#0.1.0" + ], + "workspace_root": "[ROOT]/foo" +} +"#]] + .is_json(), + ) + .run(); +} + #[cargo_test] fn patching_with_builtins_requires_feature_gate() { let p = project() diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 43f3b46bdbc..12f22e08767 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(