diff --git a/src/cargo/core/compiler/build_context/target_info.rs b/src/cargo/core/compiler/build_context/target_info.rs index 505910d5fa2..c0cb5e96163 100644 --- a/src/cargo/core/compiler/build_context/target_info.rs +++ b/src/cargo/core/compiler/build_context/target_info.rs @@ -823,6 +823,10 @@ impl<'cfg> RustcTargetData<'cfg> { } } + pub fn requested_kinds(&self) -> &[CompileKind] { + &self.requested_kinds + } + /// If a build script is overridden, this returns the `BuildOutput` to use. /// /// `lib_name` is the `links` library name and `kind` is whether it is for diff --git a/src/cargo/core/compiler/compilation.rs b/src/cargo/core/compiler/compilation.rs index 3b21e4f43fd..19f7f1af1ac 100644 --- a/src/cargo/core/compiler/compilation.rs +++ b/src/cargo/core/compiler/compilation.rs @@ -277,7 +277,7 @@ impl<'cfg> Compilation<'cfg> { // libs from the sysroot that ships with rustc. This may not be // required (at least I cannot craft a situation where it // matters), but is here to be safe. - if self.config.cli_unstable().build_std.is_none() { + if pkg.manifest().build_std().is_none() { search_path.push(self.sysroot_target_libdir[&kind].clone()); } } diff --git a/src/cargo/core/compiler/standard_lib.rs b/src/cargo/core/compiler/standard_lib.rs index 6b76a5681be..863e6c21b77 100644 --- a/src/cargo/core/compiler/standard_lib.rs +++ b/src/cargo/core/compiler/standard_lib.rs @@ -35,8 +35,12 @@ pub fn resolve_std<'cfg>( ws: &Workspace<'cfg>, target_data: &RustcTargetData<'cfg>, requested_targets: &[CompileKind], - crates: &[String], -) -> CargoResult<(PackageSet<'cfg>, Resolve, ResolvedFeatures)> { + crates_and_features: HashMap, HashSet<&str>)>, +) -> CargoResult<( + PackageSet<'cfg>, + HashMap, +)> { + let mut pkg_set: Option> = None; let src_path = detect_sysroot_src_path(target_data)?; let to_patch = [ "rustc-std-workspace-core", @@ -93,60 +97,70 @@ pub fn resolve_std<'cfg>( // `[dev-dependencies]`. No need for us to generate a `Resolve` which has // those included because we'll never use them anyway. std_ws.set_require_optional_deps(false); - // `test` is not in the default set because it is optional, but it needs - // to be part of the resolve in case we do need it. - let mut spec_pkgs = Vec::from(crates); - spec_pkgs.push("test".to_string()); - let spec = Packages::Packages(spec_pkgs); - let specs = spec.to_package_id_specs(&std_ws)?; - let features = match &config.cli_unstable().build_std_features { - Some(list) => list.clone(), - None => vec![ - "panic-unwind".to_string(), - "backtrace".to_string(), - "default".to_string(), - ], - }; - let cli_features = CliFeatures::from_command_line( - &features, /*all_features*/ false, /*uses_default_features*/ false, - )?; - let resolve = ops::resolve_ws_with_opts( - &std_ws, - target_data, - requested_targets, - &cli_features, - &specs, - HasDevUnits::No, - crate::core::resolver::features::ForceAllTargets::No, - )?; - Ok(( - resolve.pkg_set, - resolve.targeted_resolve, - resolve.resolved_features, - )) + + let ret: CargoResult<_> = crates_and_features + .into_iter() + .map(|(kind, (crates, features))| { + // `test` is not in the default set because it is optional, but it needs + // to be part of the resolve in case we do need it. + let spec_pkgs: Vec<_> = crates + .into_iter() + .chain(Some("test")) + .map(ToOwned::to_owned) + .collect(); + let spec = Packages::Packages(spec_pkgs); + let specs = spec.to_package_id_specs(&std_ws)?; + let features: Vec<_> = features.into_iter().map(ToOwned::to_owned).collect(); + let cli_features = CliFeatures::from_command_line( + &features, /*all_features*/ false, /*uses_default_features*/ false, + )?; + let resolve = ops::resolve_ws_with_opts( + &std_ws, + target_data, + requested_targets, + &cli_features, + &specs, + HasDevUnits::No, + crate::core::resolver::features::ForceAllTargets::No, + )?; + + match &mut pkg_set { + Some(pkg_set) => pkg_set.add_set(resolve.pkg_set), + pkg_set => *pkg_set = Some(resolve.pkg_set), + } + + Ok((kind, (resolve.targeted_resolve, resolve.resolved_features))) + }) + .collect(); + + let ret = ret?; + + Ok((pkg_set.unwrap(), ret)) } /// Generate a list of root `Unit`s for the standard library. /// /// The given slice of crate names is the root set. -pub fn generate_std_roots( +pub fn generate_std_units( crates: &[String], - std_resolve: &Resolve, - std_features: &ResolvedFeatures, - kinds: &[CompileKind], + std_resolve: &HashMap, + kind: CompileKind, package_set: &PackageSet<'_>, interner: &UnitInterner, profiles: &Profiles, -) -> CargoResult>> { +) -> CargoResult> { + let (std_resolve, std_features) = &std_resolve[&kind]; // Generate the root Units for the standard library. let std_ids = crates .iter() .map(|crate_name| std_resolve.query(crate_name)) .collect::>>()?; + // Convert PackageId to Package. let std_pkgs = package_set.get_many(std_ids)?; - // Generate a map of Units for each kind requested. - let mut ret = HashMap::new(); + + let mut list = Vec::new(); + for pkg in std_pkgs { let lib = pkg .targets() @@ -160,29 +174,27 @@ pub fn generate_std_roots( let mode = CompileMode::Build; let features = std_features.activated_features(pkg.package_id(), FeaturesFor::NormalOrDev); - for kind in kinds { - let list = ret.entry(*kind).or_insert_with(Vec::new); - let profile = profiles.get_profile( - pkg.package_id(), - /*is_member*/ false, - /*is_local*/ false, - unit_for, - mode, - *kind, - ); - list.push(interner.intern( - pkg, - lib, - profile, - *kind, - mode, - features.clone(), - /*is_std*/ true, - /*dep_hash*/ 0, - )); - } + let profile = profiles.get_profile( + pkg.package_id(), + /*is_member*/ false, + /*is_local*/ false, + unit_for, + mode, + kind, + ); + list.push(interner.intern( + pkg, + lib, + profile, + kind, + mode, + features.clone(), + /*is_std*/ true, + /*dep_hash*/ 0, + )); } - Ok(ret) + + Ok(list) } fn detect_sysroot_src_path(target_data: &RustcTargetData<'_>) -> CargoResult { diff --git a/src/cargo/core/compiler/unit_dependencies.rs b/src/cargo/core/compiler/unit_dependencies.rs index a3bcecc1782..53609b22936 100644 --- a/src/cargo/core/compiler/unit_dependencies.rs +++ b/src/cargo/core/compiler/unit_dependencies.rs @@ -28,8 +28,11 @@ use crate::util::interning::InternedString; use crate::util::Config; use crate::CargoResult; use log::trace; +use std::collections::hash_map::Entry; use std::collections::{HashMap, HashSet}; +use super::CompileTarget; + /// Collection of stuff used while creating the `UnitGraph`. struct State<'a, 'cfg> { ws: &'a Workspace<'cfg>, @@ -38,11 +41,10 @@ struct State<'a, 'cfg> { package_set: &'a PackageSet<'cfg>, usr_resolve: &'a Resolve, usr_features: &'a ResolvedFeatures, - std_resolve: Option<&'a Resolve>, - std_features: Option<&'a ResolvedFeatures>, - /// This flag is `true` while generating the dependencies for the standard + std_resolve: Option<&'a HashMap>, + /// This is `Some` while generating the dependencies for the standard /// library. - is_std: bool, + std: Option, global_mode: CompileMode, target_data: &'a RustcTargetData<'cfg>, profiles: &'a Profiles, @@ -60,10 +62,9 @@ pub fn build_unit_dependencies<'a, 'cfg>( package_set: &'a PackageSet<'cfg>, resolve: &'a Resolve, features: &'a ResolvedFeatures, - std_resolve: Option<&'a (Resolve, ResolvedFeatures)>, + std_resolve: Option<&'a HashMap>, roots: &[Unit], scrape_units: &[Unit], - std_roots: &HashMap>, global_mode: CompileMode, target_data: &'a RustcTargetData<'cfg>, profiles: &'a Profiles, @@ -75,10 +76,7 @@ pub fn build_unit_dependencies<'a, 'cfg>( // in the dep graph without a root. return Ok(HashMap::new()); } - let (std_resolve, std_features) = match std_resolve { - Some((r, f)) => (Some(r), Some(f)), - None => (None, None), - }; + let mut state = State { ws, config: ws.config(), @@ -87,8 +85,7 @@ pub fn build_unit_dependencies<'a, 'cfg>( usr_resolve: resolve, usr_features: features, std_resolve, - std_features, - is_std: false, + std: None, global_mode, target_data, profiles, @@ -97,16 +94,8 @@ pub fn build_unit_dependencies<'a, 'cfg>( dev_dependency_edges: HashSet::new(), }; - let std_unit_deps = calc_deps_of_std(&mut state, std_roots)?; - 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 @@ -124,55 +113,41 @@ pub fn build_unit_dependencies<'a, 'cfg>( /// Compute all the dependencies for the standard library. fn calc_deps_of_std( mut state: &mut State<'_, '_>, - std_roots: &HashMap>, -) -> CargoResult> { - if std_roots.is_empty() { - return Ok(None); - } + crates: &[String], + kind: CompileKind, +) -> CargoResult<(Vec, UnitGraph)> { + let prev_graph = std::mem::take(&mut state.unit_dependencies); + // Compute dependencies for the standard library. - state.is_std = true; - for roots in std_roots.values() { - deps_of_roots(roots, state)?; - } - state.is_std = false; - 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(), - extern_crate_name: unit.pkg.name(), - // TODO: Does this `public` make sense? - public: true, - noprelude: true, - })); - found = true; - } - } - // 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); - } - } - } + state.std = Some(kind); + + let units = crate::core::compiler::standard_lib::generate_std_units( + crates, + state.std_resolve.as_ref().unwrap(), + kind, + state.package_set, + state.interner, + state.profiles, + )?; + + deps_of_roots(&units, state)?; + + state.std = None; + + Ok(( + units, + std::mem::replace(&mut state.unit_dependencies, prev_graph), + )) } /// Compute all the dependencies of the given root units. /// The result is stored in state.unit_dependencies. fn deps_of_roots(roots: &[Unit], state: &mut State<'_, '_>) -> CargoResult<()> { + let has_test = roots + .iter() + .any(|unit| unit.mode.is_rustc_test() && unit.target.harness()); + for unit in roots.iter() { // Dependencies of tests/benches should not have `panic` set. // We check the global test mode to see if we are running in `cargo @@ -200,14 +175,20 @@ fn deps_of_roots(roots: &[Unit], state: &mut State<'_, '_>) -> CargoResult<()> { } else { UnitFor::new_normal() }; - deps_of(unit, state, unit_for)?; + deps_of(unit, state, unit_for, None, has_test)?; } Ok(()) } /// Compute the dependencies of a single unit. -fn deps_of(unit: &Unit, state: &mut State<'_, '_>, unit_for: UnitFor) -> CargoResult<()> { +fn deps_of( + unit: &Unit, + state: &mut State<'_, '_>, + unit_for: UnitFor, + prev_std_root_units: Option<&Vec>, + has_test: bool, +) -> CargoResult<()> { // Currently the `unit_dependencies` map does not include `unit_for`. This should // be safe for now. `TestDependency` only exists to clear the `panic` // flag, and you'll never ask for a `unit` with `panic` set as a @@ -215,12 +196,45 @@ fn deps_of(unit: &Unit, state: &mut State<'_, '_>, unit_for: UnitFor) -> CargoRe // requested unit's settings are the same as `Any`, `CustomBuild` can't // affect anything else in the hierarchy. if !state.unit_dependencies.contains_key(unit) { - let unit_deps = compute_deps(unit, state, unit_for)?; + let (mut unit_deps, std_root_units) = compute_deps(unit, state, unit_for, has_test)?; + + match (prev_std_root_units, &std_root_units) { + (None, None) => {} + (Some(units), None) | (None, Some(units)) => { + unit_deps.extend(units.iter().map(|unit| UnitDep { + unit: unit.clone(), + unit_for: UnitFor::new_normal(), + extern_crate_name: unit.pkg.name(), + public: true, + noprelude: true, + })); + } + (Some(units), Some(units1)) if units == units1 => { + unit_deps.extend(units.iter().map(|unit| UnitDep { + unit: unit.clone(), + unit_for: UnitFor::new_normal(), + extern_crate_name: unit.pkg.name(), + public: true, + noprelude: true, + })); + } + (Some(_), Some(_)) => { + anyhow::bail!("encountered build-std crate as a dependency of a build-std crate") + } + } + state .unit_dependencies .insert(unit.clone(), unit_deps.clone()); + for unit_dep in unit_deps { - deps_of(&unit_dep.unit, state, unit_dep.unit_for)?; + deps_of( + &unit_dep.unit, + state, + unit_dep.unit_for, + std_root_units.as_ref(), + has_test, + )?; } } Ok(()) @@ -228,18 +242,18 @@ fn deps_of(unit: &Unit, state: &mut State<'_, '_>, unit_for: UnitFor) -> CargoRe /// For a package, returns all targets that are registered as dependencies /// for that package. -/// This returns a `Vec` of `(Unit, UnitFor)` pairs. The `UnitFor` -/// is the profile type that should be used for dependencies of the unit. +/// This returns a `Vec` of `UnitDep`s, as well as the root `Unit`s of libstd from build-std. fn compute_deps( unit: &Unit, state: &mut State<'_, '_>, unit_for: UnitFor, -) -> CargoResult> { + has_test: bool, +) -> CargoResult<(Vec, Option>)> { if unit.mode.is_run_custom_build() { - return compute_deps_custom_build(unit, unit_for, state); + return compute_deps_custom_build(unit, unit_for, state).map(|x| (x, None)); } else if unit.mode.is_doc() { // Note: this does not include doc test. - return compute_deps_doc(unit, state, unit_for); + return compute_deps_doc(unit, state, unit_for).map(|x| (x, None)); } let id = unit.pkg.package_id(); @@ -315,16 +329,65 @@ fn compute_deps( // all we need. If this isn't a build script, then it depends on the // build script if there is one. if unit.target.is_custom_build() { - return Ok(ret); + return Ok((ret, None)); } ret.extend(dep_build_script(unit, unit_for, state)?); + let mut std_root_units = None; + + if let Some(crates) = unit.pkg.manifest().build_std() { + let explicit_host_kind = + CompileKind::Target(CompileTarget::new(&state.target_data.rustc.host)?); + + let mut crates = crates.to_vec(); + + // Only build libtest when libstd is built (libtest depends on libstd) + if has_test && crates.iter().any(|c| c == "std") { + crates.push("test".into()); + } + + // If this target uses build_std, we need to attach libstd's units + // to this. + let mut explicit_kinds = unit + .pkg + .explicit_compile_kinds(explicit_host_kind, state.target_data.requested_kinds()); + + if explicit_kinds.len() != 1 { + anyhow::bail!("build-std with more than one target"); + } + + let kind = explicit_kinds.pop().unwrap(); + + let (root_units, graph) = calc_deps_of_std(state, &crates, kind)?; + + if !root_units.is_empty() { + for (unit, deps) in graph { + match state.unit_dependencies.entry(unit) { + Entry::Occupied(occ) => { + if &deps != occ.get() { + panic!( + "std unit collision with existing unit:\nprev = {:?}\ncurr = {:?}", + deps, + occ.get() + ); + } + } + Entry::Vacant(v) => { + v.insert(deps); + } + } + } + } + + std_root_units = Some(root_units); + } + // If this target is a binary, test, example, etc, then it depends on // the library of the same package. The call to `resolve.deps` above // didn't include `pkg` in the return values, so we need to special case // it here and see if we need to push `(pkg, pkg_lib_target)`. if unit.target.is_lib() && unit.mode != CompileMode::Doctest { - return Ok(ret); + return Ok((ret, std_root_units)); } ret.extend(maybe_lib(unit, state, unit_for)?); @@ -369,7 +432,7 @@ fn compute_deps( ); } - Ok(ret) + Ok((ret, std_root_units)) } /// Returns the dependencies needed to run a build script. @@ -497,7 +560,7 @@ fn compute_deps_doc( for scrape_unit in state.scrape_units.iter() { // This needs to match the FeaturesFor used in cargo_compile::generate_targets. let unit_for = UnitFor::new_host(scrape_unit.target.proc_macro()); - deps_of(scrape_unit, state, unit_for)?; + deps_of(scrape_unit, state, unit_for, None, false)?; ret.push(new_unit_dep( state, scrape_unit, @@ -626,7 +689,7 @@ fn new_unit_dep( kind: CompileKind, mode: CompileMode, ) -> CargoResult { - let is_local = pkg.package_id().source_id().is_path() && !state.is_std; + let is_local = pkg.package_id().source_id().is_path() && state.std.is_none(); let profile = state.profiles.get_profile( pkg.package_id(), state.ws.is_member(pkg), @@ -659,9 +722,16 @@ fn new_unit_dep_with_profile( .is_public_dep(parent.pkg.package_id(), pkg.package_id()); let features_for = unit_for.map_to_features_for(); let features = state.activated_features(pkg.package_id(), features_for); - let unit = state - .interner - .intern(pkg, target, profile, kind, mode, features, state.is_std, 0); + let unit = state.interner.intern( + pkg, + target, + profile, + kind, + mode, + features, + state.std.is_some(), + 0, + ); Ok(UnitDep { unit, unit_for, @@ -788,16 +858,16 @@ fn connect_run_custom_build_deps(state: &mut State<'_, '_>) { impl<'a, 'cfg> State<'a, 'cfg> { fn resolve(&self) -> &'a Resolve { - if self.is_std { - self.std_resolve.unwrap() + if let Some(kind) = self.std { + &self.std_resolve.as_ref().unwrap()[&kind].0 } else { self.usr_resolve } } fn features(&self) -> &'a ResolvedFeatures { - if self.is_std { - self.std_features.unwrap() + if let Some(kind) = self.std { + &self.std_resolve.as_ref().unwrap()[&kind].1 } else { self.usr_features } diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 2d563773075..35c7c396b2f 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -412,6 +412,9 @@ features! { // Allow specifying rustflags directly in a profile (unstable, profile_rustflags, "", "reference/unstable.html#profile-rustflags-option"), + + // Allow building the standard library + (unstable, build_std, "", "reference/unstable.html#build-std"), } pub struct Feature { @@ -632,9 +635,6 @@ unstable_cli_options!( advanced_env: bool = (HIDDEN), avoid_dev_deps: bool = ("Avoid installing dev-dependencies if possible"), binary_dep_depinfo: bool = ("Track changes to dependency artifacts"), - #[serde(deserialize_with = "deserialize_build_std")] - build_std: Option> = ("Enable Cargo to compile the standard library itself as part of a crate graph compilation"), - build_std_features: Option> = ("Configure features enabled for the standard library itself when building the standard library"), config_include: bool = ("Enable the `include` key in config files"), credential_process: bool = ("Add a config setting to fetch registry authentication tokens by calling an external process"), doctest_in_workspace: bool = ("Compile doctests with paths relative to the workspace root"), @@ -712,20 +712,6 @@ const STABILIZED_WEAK_DEP_FEATURES: &str = "Weak dependency features are now alw const STABILISED_NAMESPACED_FEATURES: &str = "Namespaced features are now always available."; -fn deserialize_build_std<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, -{ - let crates = match >>::deserialize(deserializer)? { - Some(list) => list, - None => return Ok(None), - }; - let v = crates.join(","); - Ok(Some( - crate::core::compiler::standard_lib::parse_unstable_flag(Some(&v)), - )) -} - impl CliUnstable { pub fn parse( &mut self, @@ -845,10 +831,6 @@ impl CliUnstable { "mtime-on-use" => self.mtime_on_use = parse_empty(k, v)?, "named-profiles" => stabilized_warn(k, "1.57", STABILIZED_NAMED_PROFILES), "binary-dep-depinfo" => self.binary_dep_depinfo = parse_empty(k, v)?, - "build-std" => { - self.build_std = Some(crate::core::compiler::standard_lib::parse_unstable_flag(v)) - } - "build-std-features" => self.build_std_features = Some(parse_features(v)), "timings" => self.timings = Some(parse_timings(v)), "doctest-xcompile" => self.doctest_xcompile = parse_empty(k, v)?, "doctest-in-workspace" => self.doctest_in_workspace = parse_empty(k, v)?, diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index b0bc0576a7a..138c16b894e 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -53,6 +53,8 @@ pub struct Manifest { default_run: Option, metabuild: Option>, resolve_behavior: Option, + build_std: Option>, + build_std_features: Option>, } /// When parsing `Cargo.toml`, some warnings should silenced @@ -392,6 +394,8 @@ impl Manifest { original: Rc, metabuild: Option>, resolve_behavior: Option, + build_std: Option>, + build_std_features: Option>, ) -> Manifest { Manifest { summary, @@ -417,6 +421,8 @@ impl Manifest { default_run, metabuild, resolve_behavior, + build_std, + build_std_features, } } @@ -527,6 +533,15 @@ impl Manifest { })?; } + if self.build_std.is_some() || self.build_std_features.is_some() { + self.unstable_features + .require(Feature::build_std()) + .with_context(|| { + "the `package.build-std` and `package.build-std-features` \ + manifest keys are unstable and may not work properly" + })?; + } + Ok(()) } @@ -566,6 +581,16 @@ impl Manifest { .join(".metabuild") .join(format!("metabuild-{}-{}.rs", self.name(), hash)) } + + #[inline] + pub fn build_std(&self) -> Option<&Vec> { + self.build_std.as_ref() + } + + #[inline] + pub fn build_std_features(&self) -> Option<&Vec> { + self.build_std_features.as_ref() + } } impl VirtualManifest { diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index c447610f2c9..760acf35d55 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -208,6 +208,26 @@ impl Package { self.targets().iter().any(|t| t.is_example() || t.is_bin()) } + pub fn explicit_compile_kinds( + &self, + fallback_host_kind: CompileKind, + requested_kinds: &[CompileKind], + ) -> Vec { + if let Some(k) = self.manifest().forced_kind() { + vec![k] + } else { + requested_kinds + .iter() + .map(|kind| match kind { + CompileKind::Host => { + self.manifest().default_kind().unwrap_or(fallback_host_kind) + } + CompileKind::Target(t) => CompileKind::Target(*t), + }) + .collect() + } + } + pub fn serialized(&self) -> SerializedPackage { let summary = self.manifest().summary(); let package_id = summary.package_id(); diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index a21cf4f429b..7450da0d296 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -398,24 +398,49 @@ pub fn create_bcx<'a, 'cfg>( resolved_features, } = resolve; - let std_resolve_features = if let Some(crates) = &config.cli_unstable().build_std { - if build_config.build_plan { - config - .shell() - .warn("-Zbuild-std does not currently fully support --build-plan")?; + let explicit_host_kind = CompileKind::Target(CompileTarget::new(&target_data.rustc.host)?); + let std_resolve_features = { + let mut build_std_crates: HashMap, HashSet<&str>)> = + HashMap::new(); + for pkg in pkg_set.packages() { + if let Some(crates) = pkg.manifest().build_std() { + let mut kinds = + pkg.explicit_compile_kinds(explicit_host_kind, &build_config.requested_kinds); + + if kinds.len() != 1 { + anyhow::bail!("more than one target used while enabling build-std"); + } + + let kind = kinds.pop().unwrap(); + + let (requested_crates, requested_features) = + build_std_crates.entry(kind).or_default(); + requested_crates.extend(crates.iter().map(|s| &**s)); + match pkg.manifest().build_std_features() { + Some(features) => requested_features.extend(features.iter().map(|s| &**s)), + None => requested_features.extend(["panic-unwind", "backtrace", "default"]), + } + } } - if build_config.requested_kinds[0].is_host() { - // TODO: This should eventually be fixed. Unfortunately it is not - // easy to get the host triple in BuildConfig. Consider changing - // requested_target to an enum, or some other approach. - anyhow::bail!("-Zbuild-std requires --target"); + + if !build_std_crates.is_empty() { + if build_config.build_plan { + config + .shell() + .warn("build-std does not currently fully support --build-plan")?; + } + + let (std_package_set, std_resolves) = standard_lib::resolve_std( + ws, + &target_data, + &build_config.requested_kinds, + build_std_crates, + )?; + pkg_set.add_set(std_package_set); + Some(std_resolves) + } else { + None } - let (std_package_set, std_resolve, std_features) = - standard_lib::resolve_std(ws, &target_data, &build_config.requested_kinds, crates)?; - pkg_set.add_set(std_package_set); - Some((std_resolve, std_features)) - } else { - None }; // Find the packages in the resolver that the user wants to build (those @@ -472,19 +497,6 @@ pub fn create_bcx<'a, 'cfg>( workspace_resolve.as_ref().unwrap_or(&resolve), )?; - // If `--target` has not been specified, then the unit graph is built - // assuming `--target $HOST` was specified. See - // `rebuild_unit_graph_shared` for more on why this is done. - let explicit_host_kind = CompileKind::Target(CompileTarget::new(&target_data.rustc.host)?); - let explicit_host_kinds: Vec<_> = build_config - .requested_kinds - .iter() - .map(|kind| match kind { - CompileKind::Host => explicit_host_kind, - CompileKind::Target(t) => CompileKind::Target(*t), - }) - .collect(); - // Passing `build_config.requested_kinds` instead of // `explicit_host_kinds` here so that `generate_targets` can do // its own special handling of `CompileKind::Host`. It will @@ -544,33 +556,6 @@ pub fn create_bcx<'a, 'cfg>( None => Vec::new(), }; - let std_roots = if let Some(crates) = &config.cli_unstable().build_std { - // Only build libtest if it looks like it is needed. - let mut crates = crates.clone(); - if !crates.iter().any(|c| c == "test") - && units - .iter() - .any(|unit| unit.mode.is_rustc_test() && unit.target.harness()) - { - // Only build libtest when libstd is built (libtest depends on libstd) - if crates.iter().any(|c| c == "std") { - crates.push("test".to_string()); - } - } - let (std_resolve, std_features) = std_resolve_features.as_ref().unwrap(); - standard_lib::generate_std_roots( - &crates, - std_resolve, - std_features, - &explicit_host_kinds, - &pkg_set, - interner, - &profiles, - )? - } else { - Default::default() - }; - let mut unit_graph = build_unit_dependencies( ws, &pkg_set, @@ -579,7 +564,6 @@ pub fn create_bcx<'a, 'cfg>( std_resolve_features.as_ref(), &units, &scrape_units, - &std_roots, build_config.mode, &target_data, &profiles, @@ -1019,19 +1003,7 @@ fn generate_targets( // why this is done. However, if the package has its own // `package.target` key, then this gets used instead of // `$HOST` - let explicit_kinds = if let Some(k) = pkg.manifest().forced_kind() { - vec![k] - } else { - requested_kinds - .iter() - .map(|kind| match kind { - CompileKind::Host => { - pkg.manifest().default_kind().unwrap_or(explicit_host_kind) - } - CompileKind::Target(t) => CompileKind::Target(*t), - }) - .collect() - }; + let explicit_kinds = pkg.explicit_compile_kinds(explicit_host_kind, requested_kinds); for kind in explicit_kinds.iter() { let profile = profiles.get_profile( diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index d973930c73e..d8b72d216e6 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -855,6 +855,20 @@ where deserializer.deserialize_str(Visitor) } +fn deserialize_build_std<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: serde::Deserializer<'de>, +{ + let crates = match >>::deserialize(deserializer)? { + Some(list) => list, + None => return Ok(None), + }; + let v = crates.join(","); + Ok(Some( + crate::core::compiler::standard_lib::parse_unstable_flag(Some(&v)), + )) +} + /// Represents the `package`/`project` sections of a `Cargo.toml`. /// /// Note that the order of the fields matters, since this is the order they @@ -887,6 +901,10 @@ pub struct TomlProject { autotests: Option, autobenches: Option, default_run: Option, + #[serde(default)] + #[serde(deserialize_with = "deserialize_build_std")] + build_std: Option>, + build_std_features: Option>, // Package metadata. description: Option, @@ -1408,6 +1426,8 @@ impl TomlManifest { Rc::clone(me), project.metabuild.clone().map(|sov| sov.0), resolve_behavior, + project.build_std.clone(), + project.build_std_features.clone(), ); if project.license_file.is_some() && project.license.is_some() { manifest.warnings_mut().add_warning( diff --git a/tests/build-std/main.rs b/tests/build-std/main.rs index c1355b317ce..38aa53a34fa 100644 --- a/tests/build-std/main.rs +++ b/tests/build-std/main.rs @@ -22,46 +22,20 @@ use cargo_test_support::*; use std::env; use std::path::Path; -fn enable_build_std(e: &mut Execs, arg: Option<&str>) { - e.env_remove("CARGO_HOME"); - e.env_remove("HOME"); - - // And finally actually enable `build-std` for now - let arg = match arg { - Some(s) => format!("-Zbuild-std={}", s), - None => "-Zbuild-std".to_string(), - }; - e.arg(arg); - e.masquerade_as_nightly_cargo(); -} - -// Helper methods used in the tests below -trait BuildStd: Sized { - fn build_std(&mut self) -> &mut Self; - fn build_std_arg(&mut self, arg: &str) -> &mut Self; - fn target_host(&mut self) -> &mut Self; -} - -impl BuildStd for Execs { - fn build_std(&mut self) -> &mut Self { - enable_build_std(self, None); - self - } - - fn build_std_arg(&mut self, arg: &str) -> &mut Self { - enable_build_std(self, Some(arg)); - self - } - - fn target_host(&mut self) -> &mut Self { - self.arg("--target").arg(rustc_host()); - self - } -} - #[cargo_test(build_std)] fn basic() { let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.0.1" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/main.rs", " @@ -104,10 +78,9 @@ fn basic() { ) .build(); - p.cargo("check").build_std().target_host().run(); + p.cargo("check").masquerade_as_nightly_cargo().run(); p.cargo("build") - .build_std() - .target_host() + .masquerade_as_nightly_cargo() // Importantly, this should not say [UPDATING] // There have been multiple bugs where every build triggers and update. .with_stderr( @@ -115,14 +88,11 @@ fn basic() { [FINISHED] dev [..]", ) .run(); - p.cargo("run").build_std().target_host().run(); - p.cargo("test").build_std().target_host().run(); + p.cargo("run").masquerade_as_nightly_cargo().run(); + p.cargo("test").masquerade_as_nightly_cargo().run(); // Check for hack that removes dylibs. - let deps_dir = Path::new("target") - .join(rustc_host()) - .join("debug") - .join("deps"); + let deps_dir = Path::new("target").join("debug").join("deps"); assert!(p.glob(deps_dir.join("*.rlib")).count() > 0); assert_eq!(p.glob(deps_dir.join("*.dylib")).count(), 0); } @@ -133,10 +103,12 @@ fn cross_custom() { .file( "Cargo.toml", r#" + cargo-features = ["build-std"] [package] name = "foo" version = "0.1.0" edition = "2018" + build-std = ["core"] [target.custom-target.dependencies] dep = { path = "dep" } @@ -166,13 +138,24 @@ fn cross_custom() { .build(); p.cargo("build --target custom-target.json -v") - .build_std_arg("core") + .masquerade_as_nightly_cargo() .run(); } #[cargo_test(build_std)] fn custom_test_framework() { let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["core"] + "#, + ) .file( "src/lib.rs", r#" @@ -223,7 +206,63 @@ fn custom_test_framework() { let new_path = env::join_paths(paths).unwrap(); p.cargo("test --target target.json --no-run -v") + .masquerade_as_nightly_cargo() .env("PATH", new_path) - .build_std_arg("core") .run(); } + +#[cargo_test(build_std)] +fn forced_custom_target() { + // Checks how per-package-targets interct with build-std. + + let p = project() + .file( + "src/main.rs", + " + #![no_std] + #![no_main] + + #[panic_handler] + fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} + } + + pub fn foo() -> u8 { 42 } + ", + ) + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std", "per-package-target"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["core"] + forced-target = "custom-target.json" + "#, + ) + .file( + "custom-target.json", + r#" + { + "llvm-target": "x86_64-unknown-none-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "executables": true, + "panic-strategy": "abort" + } + "#, + ) + .build(); + + p.cargo("build").masquerade_as_nightly_cargo().run(); + + assert!(p.target_bin("custom-target", "foo").exists()); +} diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 5e2b5719319..39058a22dda 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -161,20 +161,12 @@ fn enable_build_std(e: &mut Execs, setup: &Setup) { // Helper methods used in the tests below trait BuildStd: Sized { fn build_std(&mut self, setup: &Setup) -> &mut Self; - fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self; fn target_host(&mut self) -> &mut Self; } impl BuildStd for Execs { fn build_std(&mut self, setup: &Setup) -> &mut Self { enable_build_std(self, setup); - self.arg("-Zbuild-std"); - self - } - - fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self { - enable_build_std(self, setup); - self.arg(format!("-Zbuild-std={}", arg)); self } @@ -192,6 +184,17 @@ fn basic() { }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/main.rs", " @@ -254,7 +257,20 @@ fn simple_lib_std() { Some(s) => s, None => return, }; - let p = project().file("src/lib.rs", "").build(); + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) + .file("src/lib.rs", "") + .build(); p.cargo("build -v") .build_std(&setup) .target_host() @@ -275,7 +291,20 @@ fn simple_bin_std() { Some(s) => s, None => return, }; - let p = project().file("src/main.rs", "fn main() {}").build(); + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); p.cargo("run -v").build_std(&setup).target_host().run(); } @@ -286,6 +315,17 @@ fn lib_nostd() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["core"] + "#, + ) .file( "src/lib.rs", r#" @@ -297,7 +337,7 @@ fn lib_nostd() { ) .build(); p.cargo("build -v --lib") - .build_std_arg(&setup, "core") + .build_std(&setup) .target_host() .with_stderr_does_not_contain("[..]libstd[..]") .run(); @@ -310,11 +350,22 @@ fn check_core() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["core"] + "#, + ) .file("src/lib.rs", "#![no_std] fn unused_fn() {}") .build(); p.cargo("check -v") - .build_std_arg(&setup, "core") + .build_std(&setup) .target_host() .with_stderr_contains("[WARNING] [..]unused_fn[..]`") .run(); @@ -341,10 +392,12 @@ fn depend_same_as_std() { .file( "Cargo.toml", r#" + cargo-features = ["build-std"] [package] name = "foo" version = "0.1.0" edition = "2018" + build-std = ["std"] [dependencies] registry-dep-using-core = "1.0" @@ -364,6 +417,17 @@ fn test() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -392,6 +456,17 @@ fn target_proc_macro() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -413,6 +488,17 @@ fn bench() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -437,6 +523,17 @@ fn doc() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -456,6 +553,17 @@ fn check_std() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", " @@ -494,6 +602,17 @@ fn doctest() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -521,6 +640,17 @@ fn no_implicit_alloc() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -551,6 +681,17 @@ fn macro_expanded_shadow() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "src/lib.rs", r#" @@ -574,7 +715,20 @@ fn ignores_incremental() { Some(s) => s, None => return, }; - let p = project().file("src/lib.rs", "").build(); + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) + .file("src/lib.rs", "") + .build(); p.cargo("build") .env("CARGO_INCREMENTAL", "1") .build_std(&setup) @@ -600,6 +754,17 @@ fn cargo_config_injects_compiler_builtins() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["core"] + "#, + ) .file( "src/lib.rs", r#" @@ -609,13 +774,6 @@ fn cargo_config_injects_compiler_builtins() { } "#, ) - .file( - ".cargo/config.toml", - r#" - [unstable] - build-std = ['core'] - "#, - ) .build(); let mut build = p.cargo("build -v --lib"); enable_build_std(&mut build, &setup); @@ -632,6 +790,18 @@ fn different_features() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + build-std-features = ["feature1"] + "#, + ) .file( "src/lib.rs", " @@ -641,11 +811,7 @@ fn different_features() { ", ) .build(); - p.cargo("build") - .build_std(&setup) - .arg("-Zbuild-std-features=feature1") - .target_host() - .run(); + p.cargo("build").build_std(&setup).target_host().run(); } #[cargo_test] @@ -671,6 +837,17 @@ fn proc_macro_only() { None => return, }; let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["build-std"] + [package] + name = "foo" + version = "0.1.0" + edition = "2018" + build-std = ["std"] + "#, + ) .file( "Cargo.toml", r#"