diff --git a/Cargo.lock b/Cargo.lock index cd8960df300..03821af3d8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -581,7 +581,7 @@ dependencies = [ [[package]] name = "cargo-util-schemas" -version = "0.14.3" +version = "0.15.0" dependencies = [ "jiff", "schemars", diff --git a/Cargo.toml b/Cargo.toml index b2fc229f6d9..5c31276f86c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ cargo-platform = { path = "crates/cargo-platform", version = "0.3.3" } cargo-test-macro = { version = "0.4.14", path = "crates/cargo-test-macro" } cargo-test-support = { version = "0.11.4", path = "crates/cargo-test-support" } cargo-util = { version = "0.2.32", path = "crates/cargo-util" } -cargo-util-schemas = { version = "0.14.2", path = "crates/cargo-util-schemas" } +cargo-util-schemas = { version = "0.15.0", path = "crates/cargo-util-schemas" } cargo-util-terminal = { version = "0.1.2", path = "crates/cargo-util-terminal" } cargo_metadata = "0.23.1" clap = "4.6.0" diff --git a/crates/cargo-util-schemas/Cargo.toml b/crates/cargo-util-schemas/Cargo.toml index 8ed89cc2f77..29c37c76146 100644 --- a/crates/cargo-util-schemas/Cargo.toml +++ b/crates/cargo-util-schemas/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-util-schemas" -version = "0.14.3" +version = "0.15.0" rust-version = "1.97" # MSRV:1 edition.workspace = true license.workspace = true diff --git a/crates/cargo-util-schemas/src/core/package_id_spec.rs b/crates/cargo-util-schemas/src/core/package_id_spec.rs index c1e636c5193..661ca81b506 100644 --- a/crates/cargo-util-schemas/src/core/package_id_spec.rs +++ b/crates/cargo-util-schemas/src/core/package_id_spec.rs @@ -233,6 +233,11 @@ fn strip_url_protocol(url: &Url) -> Url { impl fmt::Display for PackageIdSpec { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.kind() == Some(&SourceKind::Builtin) { + // Builtins have a very specific pkgid output + write!(f, "builtin://.#{}", self.name)?; + return Ok(()); + } let mut printed_name = false; match self.url { Some(ref url) => { diff --git a/crates/cargo-util-schemas/src/core/source_kind.rs b/crates/cargo-util-schemas/src/core/source_kind.rs index 3794791114d..1c16a251371 100644 --- a/crates/cargo-util-schemas/src/core/source_kind.rs +++ b/crates/cargo-util-schemas/src/core/source_kind.rs @@ -15,6 +15,8 @@ pub enum SourceKind { LocalRegistry, /// A directory-based registry. Directory, + /// Package sources distributed with the rust toolchain + Builtin, } // The hash here is important for what folder packages get downloaded into. @@ -40,6 +42,7 @@ impl SourceKind { SourceKind::SparseRegistry => None, SourceKind::LocalRegistry => Some("local-registry"), SourceKind::Directory => Some("directory"), + SourceKind::Builtin => Some("builtin"), } } } @@ -71,6 +74,10 @@ impl Ord for SourceKind { (_, SourceKind::Directory) => Ordering::Greater, (SourceKind::Git(a), SourceKind::Git(b)) => a.cmp(b), + (SourceKind::Git(_), _) => Ordering::Less, + (_, SourceKind::Git(_)) => Ordering::Greater, + + (SourceKind::Builtin, SourceKind::Builtin) => Ordering::Equal, } } } diff --git a/crates/resolver-tests/src/helpers.rs b/crates/resolver-tests/src/helpers.rs index dec0250eb02..fa9e1356027 100644 --- a/crates/resolver-tests/src/helpers.rs +++ b/crates/resolver-tests/src/helpers.rs @@ -1,8 +1,12 @@ use std::collections::BTreeMap; use std::fmt::Debug; +use std::path::Path; use std::sync::OnceLock; +use cargo::GlobalContext; +use cargo::compiler::standard_lib::detect_sysroot_src_path; use cargo::util::IntoUrl; +use cargo::util::data_structures::HashMap; use cargo::workspace::dependency::DepKind; use cargo::workspace::{Dependency, GitReference, PackageId, SourceId, Summary}; @@ -87,6 +91,29 @@ impl, U: AsRef> ToPkgId for (T, U) { } } +#[derive(Copy, Clone)] +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() + } +} + +#[derive(Copy, Clone)] +pub struct BuiltinPidWithGctx<'a> { + pub name: &'static str, + pub gctx: &'a GlobalContext, +} + +impl<'a> ToPkgId for BuiltinPidWithGctx<'a> { + fn to_pkgid(&self) -> PackageId { + PackageId::try_new(self.name, "0.0.0", builtin_loc_sysroot(self.gctx)).unwrap() + } +} + #[macro_export] macro_rules! pkg { ($pkgid:expr => [$($deps:expr),* $(,)? ]) => ({ @@ -108,6 +135,46 @@ fn registry_loc() -> SourceId { *example_dot } +fn builtin_loc() -> SourceId { + static LOCAL_PATH: OnceLock = OnceLock::new(); + let local_path = LOCAL_PATH.get_or_init(|| { + SourceId::for_builtin(Path::new(&std::env::current_dir().unwrap())).unwrap() + }); + *local_path +} + +fn builtin_loc_sysroot(gctx: &GlobalContext) -> SourceId { + static LOCAL_PATH: OnceLock = OnceLock::new(); + let local_path = LOCAL_PATH.get_or_init(|| { + SourceId::for_builtin(&detect_sysroot_src_path(gctx, None).unwrap()).unwrap() + }); + *local_path +} + +pub fn gctx_for_build_std() -> GlobalContext { + let mut gctx = GlobalContext::default().unwrap(); + gctx.nightly_features_allowed = true; + gctx.configure( + 1, + false, + None, + false, + false, + false, + &None, + &["build-std=core".to_owned()], + &[], + ) + .unwrap(); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/testsuite/mock-std/library"); + let env = HashMap::from_iter([( + "__CARGO_TESTS_ONLY_SRC_ROOT".to_owned(), + root.into_os_string().into_string().unwrap(), + )]); + gctx.set_env(env); + gctx +} + pub fn pkg(name: T) -> Summary { pkg_dep(name, Vec::new()) } @@ -215,6 +282,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); @@ -235,6 +306,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/src/lib.rs b/crates/resolver-tests/src/lib.rs index cad66c896d2..85a4b80e319 100644 --- a/crates/resolver-tests/src/lib.rs +++ b/crates/resolver-tests/src/lib.rs @@ -57,11 +57,12 @@ pub fn resolve_and_validated_raw( root_pkg_id: PackageId, sat_resolver: &mut SatResolver, ) -> CargoResult)>> { - let resolve = resolve_with_global_context_raw( + let resolve = resolve_with_raw( deps.clone(), registry, root_pkg_id, &GlobalContext::default().unwrap(), + ResolveOpts::everything(), ); match resolve { @@ -120,15 +121,31 @@ pub fn resolve_with_global_context( registry: &[Summary], gctx: &GlobalContext, ) -> CargoResult)>> { - let resolve = resolve_with_global_context_raw(deps, registry, pkg_id("root"), gctx)?; + let resolve = resolve_with_raw( + deps, + registry, + pkg_id("root"), + gctx, + ResolveOpts::everything(), + )?; Ok(collect_features(&resolve)) } -pub fn resolve_with_global_context_raw( +pub fn resolve_with_gctx_opts( + deps: Vec, + registry: &[Summary], + gctx: &GlobalContext, + opts: ResolveOpts, +) -> CargoResult { + resolve_with_raw(deps, registry, pkg_id("root"), gctx, opts) +} + +pub fn resolve_with_raw( deps: Vec, registry: &[Summary], root_pkg_id: PackageId, gctx: &GlobalContext, + opts: ResolveOpts, ) -> CargoResult { struct MyRegistry<'a> { list: &'a [Summary], @@ -190,8 +207,6 @@ pub fn resolve_with_global_context_raw( let root_summary = Summary::new(root_pkg_id, deps, &BTreeMap::new(), None::<&String>, None).unwrap(); - let opts = ResolveOpts::everything(); - let start = Instant::now(); let mut version_prefs = VersionPreferences::default(); if gctx.cli_unstable().minimal_versions { diff --git a/crates/resolver-tests/tests/resolve.rs b/crates/resolver-tests/tests/resolve.rs index 20d32fbf884..c28f8727ecd 100644 --- a/crates/resolver-tests/tests/resolve.rs +++ b/crates/resolver-tests/tests/resolve.rs @@ -1,15 +1,18 @@ +use cargo::resolver::ResolveOpts; use cargo::util::GlobalContext; use cargo::workspace::Dependency; use cargo::workspace::dependency::DepKind; +use resolver_tests::helpers::gctx_for_build_std; +use resolver_tests::helpers::{BuiltinPidWithGctx, dep_builtin}; use snapbox::assert_data_eq; 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, + names, pkg, resolve, resolve_with_gctx_opts, resolve_with_global_context, }; #[test] @@ -1036,3 +1039,51 @@ failed to select a version for `F` which could resolve this conflict "#]] ); } + +#[test] +fn test_builtin_dependency() { + let core = BuiltinPid { name: "core" }; + let reg = registry(vec![pkg!(core)]); + + let res = resolve(vec![dep_builtin("core")], ®).unwrap(); + + assert_same(&res, &names!("root", core)); +} + +#[test] +fn normal_dependency_is_not_satisfied_by_builtin_package() { + let core = BuiltinPid { name: "core" }; + let reg = registry(vec![pkg!(core)]); + + assert!(resolve(vec![dep("core")], ®).is_err()); +} + +#[test] +fn missing_builtin_dependency_errors() { + assert!(resolve(vec![dep_builtin("core")], ®istry(vec![])).is_err()); +} + +#[test] +fn injects_builtins_when_required() { + let gctx = gctx_for_build_std(); + + let core = BuiltinPidWithGctx { + name: "core", + gctx: &gctx, + }; + let compiler_builtins = BuiltinPidWithGctx { + name: "compiler_builtins", + gctx: &gctx, + }; + let reg = registry(vec![pkg!(core), pkg!(compiler_builtins)]); + + let mut opts = ResolveOpts::everything(); + opts.inject_builtins = true; + let resolve = resolve_with_gctx_opts(Vec::new(), ®, &gctx, opts).unwrap(); + + let root_deps = resolve + .deps(pkg_id("root")) + .map(|(pkg_id, _)| pkg_id) + .collect::>(); + assert_same(&root_deps, names!(core, compiler_builtins)) +} diff --git a/src/compiler/build_context/target_info.rs b/src/compiler/build_context/target_info.rs index de4b197a07f..14ce869317b 100644 --- a/src/compiler/build_context/target_info.rs +++ b/src/compiler/build_context/target_info.rs @@ -165,8 +165,6 @@ impl TargetInfo { /// invocation is cached by [`Rustc::cached_output`]. /// /// Search `Tricky` to learn why querying `rustc` several times is needed. - /// - /// When a Workspace is provided, #[tracing::instrument(skip_all)] pub fn new( gctx: &GlobalContext, diff --git a/src/compiler/standard_lib.rs b/src/compiler/standard_lib.rs index 3af824e2cb0..500302e3765 100644 --- a/src/compiler/standard_lib.rs +++ b/src/compiler/standard_lib.rs @@ -7,7 +7,7 @@ use crate::ops::{self, Packages}; use crate::resolver::HasDevUnits; use crate::resolver::Resolve; use crate::resolver::features::{CliFeatures, FeaturesFor, ResolvedFeatures}; -use crate::util::errors::CargoResult; +use crate::util::{CargoResult, GlobalContext}; use crate::workspace::profiles::{Profiles, UnitFor}; use crate::workspace::{PackageId, PackageSet, Workspace}; @@ -16,7 +16,11 @@ use std::path::PathBuf; use super::BuildConfig; -fn std_crates<'a>(crates: &'a [String], default: &'static str, units: &[Unit]) -> HashSet<&'a str> { +pub fn std_crates<'a>( + crates: &'a [String], + default: &'static str, + units: &[Unit], +) -> HashSet<&'a str> { let mut crates = HashSet::from_iter(crates.iter().map(|s| s.as_str())); // This is a temporary hack until there is a more principled way to // declare dependencies in Cargo.toml. @@ -54,12 +58,13 @@ pub fn resolve_std<'gctx>( crates: &[String], kinds: &[CompileKind], ) -> CargoResult<(PackageSet<'gctx>, Resolve, ResolvedFeatures)> { - let src_path = detect_sysroot_src_path(ws)?; + let src_path = detect_sysroot_src_path(ws.gctx(), Some(ws))?; let std_ws_manifest_path = src_path.join("Cargo.toml"); let gctx = ws.gctx(); // TODO: Consider doing something to enforce --locked? Or to prevent the // lock file from being written, such as setting ephemeral. let mut std_ws = Workspace::new(&std_ws_manifest_path, gctx)?; + std_ws.set_is_std(true); // Don't require optional dependencies in this workspace, aka std's own // `[dev-dependencies]`. No need for us to generate a `Resolve` which has // those included because we'll never use them anyway. @@ -217,15 +222,17 @@ fn generate_roots( Ok(()) } -fn detect_sysroot_src_path(ws: &Workspace<'_>) -> CargoResult { - if let Some(s) = ws.gctx().get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") { +pub fn detect_sysroot_src_path( + gctx: &GlobalContext, + ws: Option<&Workspace<'_>>, +) -> CargoResult { + if let Some(s) = gctx.get_env_os("__CARGO_TESTS_ONLY_SRC_ROOT") { return Ok(s.into()); } // NOTE: This is temporary until we figure out how to acquire the source. - let rustc = ws.gctx().load_global_rustc(Some(ws))?; - let src_path = ws - .gctx() + let rustc = gctx.load_global_rustc(ws)?; + let src_path = gctx .get_sysroot(&rustc) .expect("able to invoke rustc") .join("lib") @@ -240,7 +247,7 @@ fn detect_sysroot_src_path(ws: &Workspace<'_>) -> CargoResult { library, try:\n rustup component add rust-src", lock ); - match ws.gctx().get_env("RUSTUP_TOOLCHAIN") { + match gctx.get_env("RUSTUP_TOOLCHAIN") { Ok(rustup_toolchain) => { anyhow::bail!("{} --toolchain {}", msg, rustup_toolchain); } diff --git a/src/compiler/unit_dependencies.rs b/src/compiler/unit_dependencies.rs index 6002edfbfcc..7a9605dccb0 100644 --- a/src/compiler/unit_dependencies.rs +++ b/src/compiler/unit_dependencies.rs @@ -51,6 +51,8 @@ struct State<'a, 'gctx> { std_resolve: Option<&'a Resolve>, /// Like `usr_features` but for building standard library (`-Zbuild-std`). std_features: Option<&'a ResolvedFeatures>, + // The root units of any opaque dependencies present in the user resolve + opaque_roots: &'a HashMap>, /// `true` while generating the dependencies for the standard library. is_std: bool, /// The high-level operation requested by the user. @@ -118,6 +120,7 @@ pub fn build_unit_dependencies<'a, 'gctx>( usr_features: features, std_resolve, std_features, + opaque_roots: std_roots, is_std: false, intent, target_data, @@ -128,15 +131,14 @@ pub fn build_unit_dependencies<'a, 'gctx>( }; let std_unit_deps = calc_deps_of_std(&mut state, std_roots)?; + if let Some(std_unit_deps) = std_unit_deps { + attach_std_deps(&mut state, std_unit_deps); + } deps_of_roots(roots, &mut state)?; super::links::validate_links(state.resolve(), &state.unit_dependencies)?; // Hopefully there aren't any links conflicts with the standard library? - if let Some(std_unit_deps) = std_unit_deps { - attach_std_deps(&mut state, std_roots, std_unit_deps); - } - connect_run_custom_build_deps(&mut state); // Dependencies are used in tons of places throughout the backend, many of @@ -187,38 +189,14 @@ fn calc_deps_of_std( Ok(Some(std::mem::take(&mut state.unit_dependencies))) } -/// Add the standard library units to the `unit_dependencies`. -fn attach_std_deps( - state: &mut State<'_, '_>, - std_roots: &HashMap>, - std_unit_deps: UnitGraph, -) { - // Attach the standard library as a dependency of every target unit. - let mut found = false; - for (unit, deps) in state.unit_dependencies.iter_mut() { - if !unit.kind.is_host() && !unit.mode.is_run_custom_build() { - deps.extend(std_roots[&unit.kind].iter().map(|unit| UnitDep { - unit: unit.clone(), - unit_for: UnitFor::new_normal(unit.kind), - extern_crate_name: unit.pkg.name(), - dep_name: None, - // TODO: Does this `public` make sense? - public: true, - noprelude: true, - nounused: true, - // Artificial dependency - manifest_deps: Unhashed(None), - })); - found = true; +/// Add the dependencies of standard library units to the `unit_dependencies`. +fn attach_std_deps(state: &mut State<'_, '_>, std_unit_deps: UnitGraph) { + for (unit, deps) in std_unit_deps.into_iter() { + if unit.pkg.package_id().name() == "sysroot" { + continue; } - } - // And also include the dependencies of the standard library itself. Don't - // include these if no units actually needed the standard library. - if found { - for (unit, deps) in std_unit_deps.into_iter() { - if let Some(other_unit) = state.unit_dependencies.insert(unit, deps) { - panic!("std unit collision with existing unit: {:?}", other_unit); - } + if let Some(other_unit) = state.unit_dependencies.insert(unit, deps) { + panic!("std unit collision with existing unit: {:?}", other_unit); } } } @@ -339,17 +317,48 @@ fn compute_deps( )?; ret.push(unit_dep); } else { - let unit_dep = new_unit_dep( - state, - unit, - dep_pkg, - dep_lib, - Some(manifest_deps), - dep_unit_for, - unit.kind.for_target(dep_lib), - mode, - IS_NO_ARTIFACT_DEP, - )?; + // if builtin, return from state.opaque_roots + let unit_dep = if dep_pkg_id.source_id().is_builtin() { + if unit_for.is_for_host() { + // Build scripts/proc_macros shouldn't use build-std + continue; + } + let unit = state + .opaque_roots + .get(&unit.kind.for_target(dep_lib)) + .expect("standard library was resolved for all required targets") + .iter() + .find(|&u| u.pkg.name() == dep_pkg_id.name()); + if let Some(unit) = unit { + UnitDep { + unit: unit.clone(), + unit_for: UnitFor::new_normal(unit.kind), + extern_crate_name: unit.pkg.name(), + dep_name: None, + public: true, + noprelude: true, + nounused: true, + manifest_deps: Unhashed(None), + } + } else { + // The resolve is target-independent, so may inject implicit dependencies that + // aren't actually needed for a particular target, such as if the target does + // not support std. The standard library resolve is the source of truth here. + continue; + } + } else { + new_unit_dep( + state, + unit, + dep_pkg, + dep_lib, + Some(manifest_deps), + dep_unit_for, + unit.kind.for_target(dep_lib), + mode, + IS_NO_ARTIFACT_DEP, + )? + }; ret.push(unit_dep); } @@ -365,6 +374,33 @@ fn compute_deps( } state.dev_dependency_edges.extend(dev_deps); + // Inject "test" if required + if state.gctx.cli_unstable().build_std.is_some() + && unit.mode.is_rustc_test() + && unit.target.harness() + && !unit_for.is_for_host() + { + if let Some(test) = state + .opaque_roots + .get(&unit.kind) + .expect("standard library was resolved for all required targets") + .iter() + .find(|&u| u.pkg.name() == "test") + { + let unitdep = UnitDep { + unit: test.clone(), + unit_for: UnitFor::new_normal(test.kind), + extern_crate_name: test.pkg.name(), + dep_name: None, + public: true, + noprelude: true, + nounused: true, + manifest_deps: Unhashed(None), + }; + ret.push(unitdep); + } + } + // If this target is a build script, then what we've collected so far is // all we need. If this isn't a build script, then it depends on the // build script if there is one. @@ -649,6 +685,10 @@ fn compute_deps_doc( // the documentation of the library being built. let mut ret = Vec::new(); for (id, deps) in state.deps(unit, unit_for) { + if id.source_id().is_builtin() { + // TODO: Build-std for cargo doc is not yet implemented + continue; + } let Some(dep_lib) = calc_artifact_deps(unit, unit_for, id, &deps, state, &mut ret)? else { continue; }; diff --git a/src/ops/cargo_compile/mod.rs b/src/ops/cargo_compile/mod.rs index 0e16f487fe0..ff26fbb2581 100644 --- a/src/ops/cargo_compile/mod.rs +++ b/src/ops/cargo_compile/mod.rs @@ -473,18 +473,6 @@ pub fn create_bcx<'a, 'gctx>( // Should be fine as the loop iterate is independent of target selection selected_dep_kinds = curr_selected_dep_kinds; - if let Some(args) = target_rustc_crate_types { - override_rustc_crate_types(&mut targeted_root_units, args, interner)?; - } - - let should_scrape = - build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples; - let targeted_scrape_units = if should_scrape { - generator.generate_scrape_units(&targeted_root_units)? - } else { - Vec::new() - }; - let std_roots = if let Some(crates) = gctx.cli_unstable().build_std.as_ref() { let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap(); standard_lib::generate_std_roots( @@ -502,6 +490,32 @@ pub fn create_bcx<'a, 'gctx>( Default::default() }; + // Update the roots with std roots where needed + for root in targeted_root_units.iter_mut() { + if root.pkg.package_id().source_id().is_builtin() { + let unit = std_roots + .get(&root.kind) + .expect("standard library was resolved for all required targets") + .iter() + .find(|&u| u.pkg.name() == root.pkg.name()) + .expect("no std root found for requested package"); + //TODO: Handle and test the here properly + *root = unit.clone(); + } + } + + if let Some(args) = target_rustc_crate_types { + override_rustc_crate_types(&mut targeted_root_units, args, interner)?; + } + + let should_scrape = + build_config.intent.is_doc() && gctx.cli_unstable().rustdoc_scrape_examples; + let targeted_scrape_units = if should_scrape { + generator.generate_scrape_units(&targeted_root_units)? + } else { + Vec::new() + }; + unit_graph.extend(build_unit_dependencies( ws, &pkg_set, diff --git a/src/ops/cargo_metadata.rs b/src/ops/cargo_metadata.rs index b283e5dd604..9495cf04862 100644 --- a/src/ops/cargo_metadata.rs +++ b/src/ops/cargo_metadata.rs @@ -231,8 +231,12 @@ fn build_resolve_graph_r( let deps = { let mut dep_metadatas = Vec::new(); - let iter = resolve.deps(pkg_id).filter(|(_dep_id, deps)| { - if requested_kinds == [CompileKind::Host] { + let iter = resolve.deps(pkg_id).filter(|(dep_id, deps)| { + if dep_id.source_id().is_builtin() { + //TODO: cargo metadata behaviour is an unresolved question on the explicit builtin + //dependencies RFC + false + } else if requested_kinds == [CompileKind::Host] { true } else { requested_kinds.iter().any(|kind| { diff --git a/src/ops/cargo_tree/graph.rs b/src/ops/cargo_tree/graph.rs index 10ade176010..511721d1559 100644 --- a/src/ops/cargo_tree/graph.rs +++ b/src/ops/cargo_tree/graph.rs @@ -430,7 +430,10 @@ fn add_pkg( let from_index = graph.add_node(node); // Compute the dep name map which is later used for foo/bar feature lookups. let mut dep_name_map: HashMap> = HashMap::default(); - let mut deps: Vec<_> = resolve.deps(package_id).collect(); + let mut deps: Vec<_> = resolve + .deps(package_id) + .filter(|(dep_id, _)| !dep_id.source_id().is_builtin()) + .collect(); deps.sort_unstable_by_key(|(dep_id, _)| *dep_id); let show_all_targets = opts.target == super::Target::All; for (dep_id, deps) in deps { diff --git a/src/ops/cargo_update.rs b/src/ops/cargo_update.rs index 0fb3881e9f3..2edb0bf621d 100644 --- a/src/ops/cargo_update.rs +++ b/src/ops/cargo_update.rs @@ -1220,7 +1220,7 @@ impl PackageDiff { pub fn new(resolve: &Resolve) -> impl Iterator { let mut changes = BTreeMap::new(); let empty = Self::default(); - for dep in resolve.iter() { + for dep in resolve.iter().filter(|id| !id.source_id().is_builtin()) { changes .entry(Self::key(dep)) .or_insert_with(|| empty.clone()) @@ -1270,14 +1270,17 @@ impl PackageDiff { // Map `(package name, package source)` to `(removed versions, added versions)`. let mut changes = BTreeMap::new(); let empty = Self::default(); - for dep in previous_resolve.iter() { + for dep in previous_resolve + .iter() + .filter(|id| !id.source_id().is_builtin()) + { changes .entry(Self::key(dep)) .or_insert_with(|| empty.clone()) .removed .push(dep); } - for dep in resolve.iter() { + for dep in resolve.iter().filter(|id| !id.source_id().is_builtin()) { changes .entry(Self::key(dep)) .or_insert_with(|| empty.clone()) diff --git a/src/ops/cargo_vendor.rs b/src/ops/cargo_vendor.rs index 9cd5d7a06f8..18307a78ec2 100644 --- a/src/ops/cargo_vendor.rs +++ b/src/ops/cargo_vendor.rs @@ -154,10 +154,10 @@ fn sync( .with_context(|| format!("failed to load lockfile for {}", ws.root().display()))?; packages - .get_many(resolve.iter()) + .get_many(resolve.iter().filter(|pkg| !pkg.source_id().is_builtin())) .with_context(|| format!("failed to download packages for {}", ws.root().display()))?; - for pkg in resolve.iter() { + for pkg in resolve.iter().filter(|pkg| !pkg.source_id().is_builtin()) { let sid = source_replacement_cache.get(pkg.source_id())?; // Don't vendor path crates since they're already in the repository diff --git a/src/ops/resolve.rs b/src/ops/resolve.rs index aa5821210dd..05a122f0689 100644 --- a/src/ops/resolve.rs +++ b/src/ops/resolve.rs @@ -520,6 +520,9 @@ pub fn resolve_with_previous<'gctx>( ResolveOpts { dev_deps, features: RequestedFeatures::CliFeatures(features), + inject_builtins: ws.gctx().cli_unstable().build_std.is_some() + && !ws.is_std() + && !member.proc_macro(), }, ) }) diff --git a/src/resolver/dep_cache.rs b/src/resolver/dep_cache.rs index f2a928fe231..dfa94246e07 100644 --- a/src/resolver/dep_cache.rs +++ b/src/resolver/dep_cache.rs @@ -219,6 +219,8 @@ pub struct RegistryQueryer<'a, T: Registry> { (Option, Summary, ResolveOpts), (Rc<(HashSet, Rc>)>, bool), >, + /// The set of builtin dependencies to inject when appropriate + implicit_builtin_deps: &'a [Dependency], } impl<'a, T: Registry> RegistryQueryer<'a, T> { @@ -226,6 +228,7 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { registry: &'a T, replacements: &'a [(PackageIdSpec, Dependency)], version_prefs: &'a VersionPreferences, + implicit_builtin_deps: &'a [Dependency], ) -> Self { let inner = Rc::new(RegistryQueryerAsync::new( registry, @@ -236,6 +239,7 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { inner: inner.clone(), poller: LocalPollAdapter::new(inner), summary_cache: HashMap::default(), + implicit_builtin_deps, } } @@ -308,7 +312,13 @@ impl<'a, T: Registry> RegistryQueryer<'a, T> { // First, figure out our set of dependencies based on the requested set // of features. This also calculates what features we're going to enable // for our own dependencies. - let (used_features, deps) = resolve_features(parent, candidate, opts)?; + let (used_features, mut deps) = resolve_features(parent, candidate, opts)?; + + if opts.inject_builtins { + for dep in self.implicit_builtin_deps { + deps.push((dep.clone(), Rc::new(BTreeSet::default()))); + } + } // Next, transform all dependencies into a list of possible candidates // which can satisfy that dependency. diff --git a/src/resolver/encode.rs b/src/resolver/encode.rs index 3bc4d0921af..5ce86637871 100644 --- a/src/resolver/encode.rs +++ b/src/resolver/encode.rs @@ -514,6 +514,7 @@ impl ser::Serialize for Resolve { let encodable = ids .iter() + .filter(|&p_id| !p_id.source_id().is_builtin()) .map(|&id| encodable_resolve_node(id, self, &state)) .collect::>(); @@ -607,6 +608,7 @@ fn encodable_resolve_node( None => { let mut deps = resolve .deps_not_replaced(id) + .filter(|(id, _)| !id.source_id().is_builtin()) .map(|(id, _)| encodable_package_id(id, state, resolve.version())) .collect::>(); deps.sort(); @@ -661,7 +663,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/resolver/mod.rs b/src/resolver/mod.rs index ce1ca941efc..861a5423291 100644 --- a/src/resolver/mod.rs +++ b/src/resolver/mod.rs @@ -58,7 +58,10 @@ //! that we're implementing something that probably shouldn't be allocating all //! over the place. +use crate::compiler::standard_lib::{detect_sysroot_src_path, std_crates}; use crate::util::data_structures::{HashMap, HashSet}; +use crate::util::interning::InternedString; +use crate::workspace::dependency::DepKind; use rustc_hash::FxBuildHasher; use std::collections::BTreeMap; use std::rc::Rc; @@ -134,7 +137,31 @@ pub fn resolve( .cli_unstable() .direct_minimal_versions .then_some(VersionOrdering::MinimumVersionsFirst); - let mut registry = RegistryQueryer::new(registry, replacements, version_prefs); + + let implicit_builtin_deps: Vec = if summaries + .iter() + .any(|(_, opts)| opts.inject_builtins) + { + let crates = gctx.cli_unstable().build_std.as_deref().unwrap_or_default(); + // We default to "std" here as we don't yet know the default crates for any of the targets + // we're building for. Unit generation will discard any builtin Summaries that are not + // required for each target + let crates = std_crates(crates, "std", &[]); + let sysroot_src = detect_sysroot_src_path(gctx, None)?; + crates + .iter() + .map(|&name| Dependency::new_implicit_builtin(InternedString::from(name), &sysroot_src)) + .collect::>>()? + } else { + vec![] + }; + + let mut registry = RegistryQueryer::new( + registry, + replacements, + version_prefs, + &implicit_builtin_deps, + ); // Global cache of the reasons for each time we backtrack. let mut past_conflicting_activations = conflict_cache::ConflictCache::new(); @@ -240,7 +267,7 @@ fn activate_deps_loop( while let Some((just_here_for_the_error_messages, frame)) = remaining_deps.pop_most_constrained() { - let (mut parent, (mut dep, candidates, mut features)) = frame; + let (mut parent, parent_inject_builtins, (mut dep, candidates, mut features)) = frame; // If we spend a lot of time here (we shouldn't in most cases) then give // a bit of a visual indicator as to what we're doing. @@ -393,12 +420,20 @@ fn activate_deps_loop( }; let pid = candidate.package_id(); + // The deps frame inject_builtins field is inherited from the parent, and is a baseline + // for all siblings. We override that baseline to false for a dep that's already builtin + // or for the host + let inject_builtins = parent_inject_builtins + && !dep.source_id().is_builtin() + && dep.kind() != DepKind::Build; + let opts = ResolveOpts { dev_deps: false, features: RequestedFeatures::DepFeatures { features: Rc::clone(&features), uses_default_features: dep.uses_default_features(), }, + inject_builtins, }; trace!( "{}[{}]>{} trying {}", @@ -689,6 +724,7 @@ fn activate( let frame = DepsFrame { parent: candidate, just_for_error_messages: false, + inject_builtins: opts.inject_builtins, remaining_siblings: RcVecIter::new(Rc::clone(deps)), }; Ok(Some((frame, now.elapsed()))) diff --git a/src/resolver/types.rs b/src/resolver/types.rs index ebd864cc29b..056e152cb37 100644 --- a/src/resolver/types.rs +++ b/src/resolver/types.rs @@ -136,7 +136,7 @@ impl ResolveBehavior { } } -/// Options for how the resolve should work. +/// Options for how a Summary should be activated during the resolve #[derive(Clone, Debug, Eq, PartialEq, Hash)] pub struct ResolveOpts { /// Whether or not dev-dependencies should be included. @@ -146,6 +146,10 @@ pub struct ResolveOpts { pub dev_deps: bool, /// Set of features requested on the command-line. pub features: RequestedFeatures, + /// Whether or not to inject builtin dependencies. Host deps like proc_macros and build scripts + /// should not use build-std, so therefore build-dependencies and transitive + /// dependencies via build-dependencies do not have builtin dependencies. + pub inject_builtins: bool, } impl ResolveOpts { @@ -154,11 +158,16 @@ impl ResolveOpts { ResolveOpts { dev_deps: true, features: RequestedFeatures::CliFeatures(CliFeatures::new_all(true)), + inject_builtins: false, } } - pub fn new(dev_deps: bool, features: RequestedFeatures) -> ResolveOpts { - ResolveOpts { dev_deps, features } + pub fn new(dev_deps: bool, features: RequestedFeatures, inject_builtins: bool) -> ResolveOpts { + ResolveOpts { + dev_deps, + features, + inject_builtins, + } } } @@ -218,6 +227,7 @@ impl PackageId { pub struct DepsFrame { pub parent: Summary, pub just_for_error_messages: bool, + pub inject_builtins: bool, pub remaining_siblings: RcVecIter, } @@ -294,7 +304,7 @@ impl RemainingDeps { self.data.insert((x, insertion_time)); self.time += 1; } - pub fn pop_most_constrained(&mut self) -> Option<(bool, (Summary, DepInfo))> { + pub fn pop_most_constrained(&mut self) -> Option<(bool, (Summary, bool, DepInfo))> { while let Some((mut deps_frame, insertion_time)) = self.data.remove_min() { let just_here_for_the_error_messages = deps_frame.just_for_error_messages; @@ -304,8 +314,12 @@ impl RemainingDeps { let sibling = deps_frame.remaining_siblings.iter().next().cloned(); if let Some(sibling) = sibling { let parent = Summary::clone(&deps_frame.parent); + let inject_builtins = deps_frame.inject_builtins; self.data.insert((deps_frame, insertion_time)); - return Some((just_here_for_the_error_messages, (parent, sibling))); + return Some(( + just_here_for_the_error_messages, + (parent, inject_builtins, sibling), + )); } } None diff --git a/src/sources/builtin.rs b/src/sources/builtin.rs new file mode 100644 index 00000000000..023b72ebbc6 --- /dev/null +++ b/src/sources/builtin.rs @@ -0,0 +1,110 @@ +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.is_opaque() { + // Avoid loading packages in the path source if it's not needed + return Ok(()); + } + self.path_source + .query(dep, kind, &mut |summary| { + let summary = match summary { + IndexSummary::Candidate(summary) => { + let package_id = summary.package_id(); + let opaque = self + .opaque_summaries + .borrow_mut() + .entry(package_id) + .or_insert_with(|| Summary::new_opaque(package_id, self.source_id)) + .clone(); + IndexSummary::Candidate(opaque) + } + summary => summary, + }; + f(summary); + }) + .await + } + + fn supports_checksums(&self) -> bool { + self.path_source.supports_checksums() + } + + fn requires_precise(&self) -> bool { + self.path_source.requires_precise() + } + + fn source_id(&self) -> SourceId { + self.source_id + } + + async fn download(&self, id: PackageId) -> CargoResult { + self.path_source.download(id).await + } + + async fn finish_download(&self, id: PackageId, data: Vec) -> CargoResult { + self.path_source.finish_download(id, data).await + } + + fn fingerprint(&self, pkg: &Package) -> CargoResult { + self.path_source.fingerprint(pkg) + } + + fn describe(&self) -> String { + self.source_id.to_string() + } + + fn invalidate_cache(&self) { + // The RecursivePathSource does not clear its cached Packages, meaning nothing can + // invalidate our cached summaries + } + + fn set_quiet(&mut self, quiet: bool) { + self.path_source.set_quiet(quiet); + } +} diff --git a/src/sources/mod.rs b/src/sources/mod.rs index 5d92c92770b..999d4cf2b58 100644 --- a/src/sources/mod.rs +++ b/src/sources/mod.rs @@ -26,6 +26,7 @@ //! //! [source replacement]: https://doc.rust-lang.org/nightly/cargo/reference/source-replacement.html +pub use self::builtin::BuiltinSource; pub use self::config::SourceConfigMap; pub use self::directory::DirectorySource; pub use self::git::GitSource; @@ -37,6 +38,7 @@ pub use self::registry::{ }; pub use self::replaced::ReplacedSource; +pub mod builtin; pub mod config; pub mod directory; pub mod git; diff --git a/src/sources/path.rs b/src/sources/path.rs index 1d235237bc3..ac376106e73 100644 --- a/src/sources/path.rs +++ b/src/sources/path.rs @@ -1184,12 +1184,15 @@ fn read_nested_packages( // Registry sources are not allowed to have `path=` dependencies because // they're all translated to actual registry dependencies. // + // The standard library source intentionally does not include some test crates leading to broken + // dev-dependencies links. The main directory walk already gets all the packages we need. + // // We normalize the path here ensure that we don't infinitely walk around // looking for crates. By normalizing we ensure that we visit this crate at // most once. // // TODO: filesystem/symlink implications? - if !source_id.is_registry() { + if !source_id.is_registry() && !source_id.is_builtin() { for p in nested.iter() { let path = paths::normalize_path(&path.join(p)); let result = diff --git a/src/workspace/dependency.rs b/src/workspace/dependency.rs index b4f4b9c8095..7f6ba4071d2 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,10 +166,35 @@ impl Dependency { platform: None, explicit_name_in_toml: None, artifact: None, + opaque: source_id.is_builtin(), // All deps on builtin packages are opaque }), } } + pub fn new_implicit_builtin(name: InternedString, path: &Path) -> CargoResult { + Ok(Dependency { + inner: Arc::new(Inner { + name, + source_id: SourceId::for_builtin(path)?, + registry_id: None, + req: OptVersionReq::Any, + kind: DepKind::Normal, + only_match_name: false, + optional: false, + public: true, + // Note that the feature specifications here will be thrown away during Unit + // generation if `-Zbuild-std-features` is enabled + features: Vec::new(), + default_features: false, + specified_req: false, + platform: None, + explicit_name_in_toml: None, + artifact: None, + opaque: true, + }), + }) + } + pub fn serialized( &self, unstable_flags: &CliUnstable, @@ -412,6 +441,10 @@ impl Dependency { self.inner.optional } + pub fn is_opaque(&self) -> bool { + self.inner.opaque + } + /// Returns `true` if the default features of the dependency are requested. pub fn uses_default_features(&self) -> bool { self.inner.default_features diff --git a/src/workspace/source_id.rs b/src/workspace/source_id.rs index dbfbc367dae..5811c38fee1 100644 --- a/src/workspace/source_id.rs +++ b/src/workspace/source_id.rs @@ -1,7 +1,9 @@ use crate::context; use crate::sources::registry::CRATES_IO_HTTP_INDEX; use crate::sources::source::Source; -use crate::sources::{CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY, DirectorySource}; +use crate::sources::{ + BuiltinSource, CRATES_IO_DOMAIN, CRATES_IO_INDEX, CRATES_IO_REGISTRY, DirectorySource, +}; use crate::sources::{GitSource, PathSource, RegistrySource}; use crate::util::data_structures::HashSet; use crate::util::interning::InternedString; @@ -204,6 +206,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. @@ -345,6 +355,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 { @@ -403,6 +418,14 @@ impl SourceId { } Ok(Box::new(PathSource::new(&path, self, gctx))) } + 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)?)) } @@ -663,6 +686,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()) } 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 } diff --git a/src/workspace/workspace.rs b/src/workspace/workspace.rs index 98b4e6d9d10..1c8f7cac238 100644 --- a/src/workspace/workspace.rs +++ b/src/workspace/workspace.rs @@ -101,6 +101,9 @@ pub struct Workspace<'gctx> { /// `cargo install` or `cargo package` commands. is_ephemeral: bool, + /// `true` if this is the standard library's workspace + is_std: bool, + /// `true` if this workspace should enforce optional dependencies even when /// not needed; false if this workspace should only enforce dependencies /// needed by the current configuration (such as in cargo install). In some @@ -260,6 +263,7 @@ impl<'gctx> Workspace<'gctx> { member_ids: HashSet::default(), default_members: Vec::new(), is_ephemeral: false, + is_std: false, require_optional_deps: true, loaded_packages: RefCell::new(HashMap::default()), ignore_lock: false, @@ -703,6 +707,14 @@ impl<'gctx> Workspace<'gctx> { self.is_ephemeral } + pub fn set_is_std(&mut self, is_std: bool) { + self.is_std = is_std; + } + + pub fn is_std(&self) -> bool { + self.is_std + } + pub fn require_optional_deps(&self) -> bool { self.require_optional_deps } diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index acccdd4699d..e4839780aee 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( @@ -261,7 +265,11 @@ fn shared_std_dependency_rebuild() { [build-dependencies] dep_test = {{ path = \"{}/tests/testsuite/mock-std/dep_test\" }} + + [dependencies] + dep_test = {{ path = \"{}/tests/testsuite/mock-std/dep_test\" }} ", + manifest_dir.replace('\\', "/"), manifest_dir.replace('\\', "/") ) .as_str(), @@ -284,27 +292,30 @@ fn shared_std_dependency_rebuild() { ) .build(); + // One build each for the: + // - build-std build + // - build-dependency (for the host, with the sysroot std) + // - dependency (for the host, with build-std std) p.cargo("build -v") .build_std(&setup) - .target_host() .with_stderr_data(str![[r#" ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... [RUNNING] `[..] rustc --crate-name dep_test [..]` ... +[RUNNING] `[..] rustc --crate-name dep_test [..]` +... "#]]) .run(); + // Sanity check that all artifacts are reused. + // TODO: This test used to test that the build-dependency would be shared between `--target=host` and + // non-`--target` invocations, but that isn't currently testable as RUSTFLAGS set in this file's + // test harness apply to the non-cross-compile mode. p.cargo("build -v") .build_std(&setup) - .with_stderr_does_not_contain(str![[r#" - ... - [RUNNING] `[..] rustc --crate-name dep_test [..]` - ... - [RUNNING] `[..] rustc --crate-name dep_test [..]` - ... - "#]]) + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name dep_test [..]`") .run(); } @@ -384,6 +395,161 @@ fn check_core() { .run(); } +#[cargo_test(build_std_mock)] +fn build_std_does_not_change_lockfile() { + let setup = setup(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + "#, + ) + .file( + "src/main.rs", + r#" + fn main() { + std::custom_api(); + } + "#, + ) + .build(); + + p.cargo("generate-lockfile").run(); + let lockfile = p.read_lockfile(); + + p.cargo("build").build_std(&setup).run(); + + let build_std_lockfile = p.read_lockfile(); + assert_eq!(lockfile, build_std_lockfile); + + // Demonstrate the positive check actually works + assert!(build_std_lockfile.contains("name = \"foo\"")); + + assert!(!build_std_lockfile.contains("name = \"core\"")); + assert!(!build_std_lockfile.contains("name = \"std\"")); + assert!(!build_std_lockfile.contains("name = \"alloc\"")); + assert!(!build_std_lockfile.contains("name = \"object\"")); + assert!(!build_std_lockfile.contains("name = \"libc\"")); +} + +#[cargo_test(build_std_mock)] +fn builtins_do_not_show_in_status_messages() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + // New lockfile + p.cargo("c") + .build_std_arg(&setup, "core") + .with_stderr_contains("[LOCKING] 1 package [..]") + .with_stderr_does_not_contain("[ADDING] core") + .run(); + + // Updating lockfile + p.cargo("add registry-dep-using-alloc") + .build_std_arg(&setup, "core,alloc") + .with_stderr_contains("[ADDING] registry-dep-using-alloc [..]") + .with_stderr_contains("[LOCKING] 1 package [..]") + .run(); +} + +#[cargo_test(build_std_mock)] +fn builtins_do_not_show_in_metadata() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + p.cargo("metadata") + .build_std_arg(&setup, "core") + .with_stdout_contains("[..]registry-dep-using-core[..]") + .with_stdout_does_not_contain("[..]builtin[..]") + .run(); +} + +#[cargo_test(build_std_mock)] +fn builtins_do_not_show_in_tree() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + p.cargo("tree") + .build_std_arg(&setup, "core") + .with_stdout_contains("[..]registry-dep-using-core[..]") + .with_stdout_does_not_contain("[..]builtin[..]") + .run(); +} + +#[cargo_test(build_std_mock)] +fn builtins_are_not_vendored() { + let setup = setup(); + let p = project() + .file("src/lib.rs", "#![no_std]") + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + + [dependencies] + registry-dep-using-core = "1.0" + "#, + ) + .build(); + + p.cargo("vendor --respect-source-config") + .build_std_arg(&setup, "core") + .run(); + + assert!( + p.root() + .join("vendor/registry-dep-using-core/Cargo.toml") + .is_file() + ); + assert!(!p.root().join("vendor/core").exists()); + assert!(!p.root().join("vendor/compiler_builtins").exists()); +} + #[cargo_test(build_std_mock)] fn build_std_with_no_arg_for_core_only_target() { let target = "aarch64-unknown-none"; @@ -481,6 +647,9 @@ fn build_std_with_no_arg_for_core_only_target() { "#]] .unordered(), ) + .with_stderr_does_not_contain( + "[RUNNING] `[..]rustc --crate-name alloc [..]--target aarch64-unknown-none[..]`", + ) .run(); } @@ -890,3 +1059,38 @@ fn std_build_script_metadata_propagate_to_user() { p.cargo("check").build_std(&setup).target_host().run(); } + +#[cargo_test(build_std_mock)] +fn std_build_dash_p() { + let setup = setup(); + + let p = project().file("src/main.rs", "fn main() {}").build(); + p.cargo("b -v -p std") + .build_std(&setup) + .with_stderr_contains("[RUNNING] `[..] rustc --crate-name std [..]`") + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name foo [..]`") + .run(); + + p.cargo("b -v -p foo") + .build_std(&setup) + .with_stderr_contains("[RUNNING] `[..] rustc --crate-name foo [..]`") + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name std [..]`") + .run(); + + p.cargo("clean -v -p std") + .build_std(&setup) + .with_stderr_contains("[REMOVED] [FILE_NUM] files, [FILE_SIZE]B total") + .run(); + + p.cargo("b -v -p foo") + .build_std(&setup) + .with_stderr_data(str![[r#" +... +[RUNNING] `[..] rustc --crate-name std [..]` +... +[RUNNING] `[..] rustc --crate-name foo [..]` +... +"#]]) + .with_stderr_does_not_contain("[RUNNING] `[..] rustc --crate-name core [..]`") + .run(); +} diff --git a/tests/testsuite/unit_graph.rs b/tests/testsuite/unit_graph.rs index cead13bc6e8..9d9448a354e 100644 --- a/tests/testsuite/unit_graph.rs +++ b/tests/testsuite/unit_graph.rs @@ -1,5 +1,7 @@ //! Tests for --unit-graph option. +use std::path::Path; + use crate::prelude::*; use cargo_test_support::project; use cargo_test_support::registry::Package; @@ -237,3 +239,243 @@ fn simple() { ) .run(); } + +#[cargo_test] +fn builtins() { + crate::standard_lib::publish_mock_std_registry_packages(); + let p = project().file("src/lib.rs", "#![no_std]").build(); + let mock_std = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std/library"); + + p.cargo("build --unit-graph -Zunstable-options -Zbuild-std=core") + .env("__CARGO_TESTS_ONLY_SRC_ROOT", mock_std) + .masquerade_as_nightly_cargo(&["unit-graph", "build-std"]) + .with_stdout_data( + str![[r#" +{ + "roots": [ + 4 + ], + "units": [ + { + "dependencies": [ + { + "extern_crate_name": "build_script_build", + "index": 2, + "noprelude": false, + "nounused": false, + "public": false + } + ], + "features": [], + "is_std": true, + "mode": "build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/compiler_builtins#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2018", + "kind": [ + "lib" + ], + "name": "compiler_builtins", + "src_path": "[..]/tests/testsuite/mock-std/library/compiler_builtins/src/lib.rs", + "test": true + } + }, + { + "dependencies": [], + "features": [], + "is_std": true, + "mode": "build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/compiler_builtins#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 0, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "bin" + ], + "doc": false, + "doctest": false, + "edition": "2018", + "kind": [ + "custom-build" + ], + "name": "build-script-build", + "src_path": "[..]/tests/testsuite/mock-std/library/compiler_builtins/build.rs", + "test": false + } + }, + { + "dependencies": [ + { + "extern_crate_name": "build_script_build", + "index": 1, + "noprelude": false, + "nounused": false, + "public": false + } + ], + "features": [], + "is_std": true, + "mode": "run-custom-build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/compiler_builtins#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": false, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "bin" + ], + "doc": false, + "doctest": false, + "edition": "2018", + "kind": [ + "custom-build" + ], + "name": "build-script-build", + "src_path": "[..]/tests/testsuite/mock-std/library/compiler_builtins/build.rs", + "test": false + } + }, + { + "dependencies": [], + "features": [], + "is_std": true, + "mode": "build", + "pkg_id": "path+file://[..]/tests/testsuite/mock-std/library/core#0.1.0", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2018", + "kind": [ + "lib" + ], + "name": "core", + "src_path": "[..]/tests/testsuite/mock-std/library/core/src/lib.rs", + "test": true + } + }, + { + "dependencies": [ + { + "extern_crate_name": "compiler_builtins", + "index": 0, + "noprelude": true, + "nounused": true, + "public": true + }, + { + "extern_crate_name": "core", + "index": 3, + "noprelude": true, + "nounused": true, + "public": true + } + ], + "features": [], + "mode": "build", + "pkg_id": "path+[ROOTURL]/foo#0.0.1", + "platform": null, + "profile": { + "codegen_backend": null, + "codegen_units": null, + "debug_assertions": true, + "debuginfo": 2, + "incremental": false, + "lto": "false", + "name": "dev", + "opt_level": "0", + "overflow_checks": true, + "panic": "unwind", + "rpath": false, + "split_debuginfo": "{...}", + "strip": "{...}" + }, + "target": { + "crate_types": [ + "lib" + ], + "doc": true, + "doctest": true, + "edition": "2015", + "kind": [ + "lib" + ], + "name": "foo", + "src_path": "[ROOT]/foo/src/lib.rs", + "test": true + } + } + ], + "version": 1 +} +"#]] + .is_json(), + ) + .run(); +}