diff --git a/Cargo.toml b/Cargo.toml index e23f4ec066e..b6d08aa1b33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ num_cpus = "1.0" opener = "0.5" os_info = "3.0.7" percent-encoding = "2.0" +regex = "1" # Issue 9451 rustfix = "0.6.0" semver = { version = "1.0.3", features = ["serde"] } serde = { version = "1.0.123", features = ["derive"] } diff --git a/ci/validate-man.sh b/ci/validate-man.sh index 92df4978143..7c0eb097499 100755 --- a/ci/validate-man.sh +++ b/ci/validate-man.sh @@ -2,7 +2,6 @@ # This script validates that there aren't any changes to the man pages. set -e - cd src/doc changes=$(git status --porcelain) diff --git a/src/cargo/core/compiler/build_config.rs b/src/cargo/core/compiler/build_config.rs index 00733f38ab8..6dc25d1fdfc 100644 --- a/src/cargo/core/compiler/build_config.rs +++ b/src/cargo/core/compiler/build_config.rs @@ -57,6 +57,7 @@ impl BuildConfig { mode: CompileMode, ) -> CargoResult { let cfg = config.build_config()?; + let requested_kinds = CompileKind::from_requested_targets(config, requested_targets)?; if jobs == Some(0) { anyhow::bail!("jobs must be at least 1") @@ -105,6 +106,10 @@ impl BuildConfig { _ => bail!("only one `--target` argument is supported"), } } + + pub fn set_requested_kind_at(&mut self, k: CompileKind, idx: usize) { + self.requested_kinds[idx] = k; + } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 04d6d0a94e2..d0527fce8a6 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -17,6 +17,9 @@ use std::fmt; pub use crate::util::errors::{InternalError, VerboseError}; pub use crate::util::{indented_lines, CargoResult, CliError, CliResult, Config}; +// Tests +pub use crate::ops::{PerPackageTarget, PerPackageTargetMode}; + pub const CARGO_ENV: &str = "CARGO"; #[macro_use] diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 392cef74085..a7691bbd1ff 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -47,6 +47,11 @@ use crate::util::{closest_msg, profile, CargoResult, StableHasher}; use anyhow::{bail, Context as _}; +// #9451 +use regex::Regex; +use std::fs::read_to_string; +use std::path::Path; + /// Contains information about how a package should be compiled. /// /// Note on distinction between `CompileOptions` and `BuildConfig`: @@ -55,6 +60,74 @@ use anyhow::{bail, Context as _}; /// of it as `CompileOptions` are high-level settings requested on the /// command-line, and `BuildConfig` are low-level settings for actually /// driving `rustc`. + +// #9451 +#[derive(Debug, Clone)] +pub enum PerPackageTargetMode { + DefaultTarget, + ForcedTarget, +} + +// #9451 +#[derive(Debug, Clone)] +pub struct PerPackageTarget { + pub mode: PerPackageTargetMode, + pub target_triple: String, +} + +// #9451 +impl PerPackageTarget { + pub fn new(p: &str) -> Option { + let manifest_file = read_to_string(Path::new(p)); + + match manifest_file { + Ok(f) => { + let default_target_re = Regex::new(r"(?m)^default-target.*").unwrap(); + let forced_target_re = Regex::new(r"(?m)^forced-target.*").unwrap(); + + if default_target_re.is_match(f.as_str()) { + Some(PerPackageTarget { + mode: PerPackageTargetMode::DefaultTarget, + target_triple: String::from( + default_target_re + .captures(f.as_str()) + .unwrap() + .get(0) + .map_or("", |m| m.as_str()) + .split_whitespace() + .last() + .unwrap(), + ) + .split("\"") + .collect::>()[1] + .to_string(), + }) + } else if forced_target_re.is_match(f.as_str()) { + Some(PerPackageTarget { + mode: PerPackageTargetMode::ForcedTarget, + target_triple: String::from( + default_target_re + .captures(f.as_str()) + .unwrap() + .get(0) + .map_or("", |m| m.as_str()) + .split_whitespace() + .last() + .unwrap(), + ) + .split("\"") + .collect::>()[1] + .to_string(), + }) + } else { + None + } + } + Err(_) => None, + } + } +} + #[derive(Debug)] pub struct CompileOptions { /// Configuration information for a rustc build @@ -83,19 +156,37 @@ pub struct CompileOptions { impl<'a> CompileOptions { pub fn new(config: &Config, mode: CompileMode) -> CargoResult { - Ok(CompileOptions { - build_config: BuildConfig::new(config, None, &[], mode)?, - cli_features: CliFeatures::new_all(false), - spec: ops::Packages::Packages(Vec::new()), - filter: CompileFilter::Default { - required_features_filterable: false, - }, - target_rustdoc_args: None, - target_rustc_args: None, - local_rustdoc_args: None, - rustdoc_document_private_items: false, - honor_rust_version: true, - }) + let ppt = PerPackageTarget::new("Cargo.toml"); + if ppt.is_some() { + let per_package = ppt.unwrap(); // already know it's a Some at this point + Ok(CompileOptions { + build_config: BuildConfig::new(config, None, &[per_package.target_triple], mode)?, + cli_features: CliFeatures::new_all(false), + spec: ops::Packages::Packages(Vec::new()), + filter: CompileFilter::Default { + required_features_filterable: false, + }, + target_rustdoc_args: None, + target_rustc_args: None, + local_rustdoc_args: None, + rustdoc_document_private_items: false, + honor_rust_version: true, + }) + } else { + Ok(CompileOptions { + build_config: BuildConfig::new(config, None, &[], mode)?, + cli_features: CliFeatures::new_all(false), + spec: ops::Packages::Packages(Vec::new()), + filter: CompileFilter::Default { + required_features_filterable: false, + }, + target_rustdoc_args: None, + target_rustc_args: None, + local_rustdoc_args: None, + rustdoc_document_private_items: false, + honor_rust_version: true, + }) + } } } @@ -361,7 +452,38 @@ pub fn create_bcx<'a, 'cfg>( } config.validate_term_config()?; - let target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?; + // If `--target` has not been specified and per-package-target isn't enabled in Cargo,toml, + // then the unit graph is built assuming `--target $HOST` was specified. See + // `rebuild_unit_graph_shared` for more on why this is done. + let ppt = PerPackageTarget::new("Cargo.toml"); + + let mut explicit_host_kinds: Vec<_> = Vec::new(); + let host_target_data = RustcTargetData::new(ws, &build_config.requested_kinds)?; + + let explicit_host_kind = match ppt { + Some(ref target) => match target.mode { + PerPackageTargetMode::DefaultTarget => { + let compile_kind = + CompileKind::Target(CompileTarget::new(target.target_triple.as_str())?); + explicit_host_kinds.push(compile_kind); + compile_kind + } + + PerPackageTargetMode::ForcedTarget => { + let compile_kind = + CompileKind::Target(CompileTarget::new(target.target_triple.as_str())?); + explicit_host_kinds.push(compile_kind); + compile_kind + } + }, + None => CompileKind::Target(CompileTarget::new(&host_target_data.rustc.host)?), + }; + + // In case it wasn't already obvious enough: explicit_host_kinds + // *now* contains not the data from the build_config argument passed + // to this function, but instead to the empty Vec that is pushed to + // if per-package-target is enabled. + let per_package_target_data = RustcTargetData::new(ws, &explicit_host_kinds)?; let all_packages = &Packages::All; let rustdoc_scrape_examples = &config.cli_unstable().rustdoc_scrape_examples; @@ -378,15 +500,27 @@ pub fn create_bcx<'a, 'cfg>( } else { HasDevUnits::No }; - let resolve = ops::resolve_ws_with_opts( - ws, - &target_data, - &build_config.requested_kinds, - cli_features, - &resolve_specs, - has_dev_units, - crate::core::resolver::features::ForceAllTargets::No, - )?; + let resolve = match ppt { + Some(_) => ops::resolve_ws_with_opts( + ws, + &per_package_target_data, + &explicit_host_kinds, + cli_features, + &resolve_specs, + has_dev_units, + crate::core::resolver::features::ForceAllTargets::No, + )?, + None => ops::resolve_ws_with_opts( + ws, + &host_target_data, + &build_config.requested_kinds, + cli_features, + &resolve_specs, + has_dev_units, + crate::core::resolver::features::ForceAllTargets::No, + )?, + }; + let WorkspaceResolve { mut pkg_set, workspace_resolve, @@ -394,26 +528,6 @@ 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")?; - } - 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"); - } - 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 // passed in with `-p` or the defaults from the workspace), and convert // Vec to a Vec. @@ -468,38 +582,75 @@ 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 // internally replace the host kind by the `explicit_host_kind` // before setting as a unit. - let mut units = generate_targets( - ws, - &to_builds, - filter, - &build_config.requested_kinds, - explicit_host_kind, - build_config.mode, - &resolve, - &workspace_resolve, - &resolved_features, - &pkg_set, - &profiles, - interner, - )?; + let mut units = match ppt { + Some(_) => generate_targets( + ws, + &to_builds, + filter, + &explicit_host_kinds, + explicit_host_kind, + build_config.mode, + &resolve, + &workspace_resolve, + &resolved_features, + &pkg_set, + &profiles, + interner, + )?, + None => generate_targets( + ws, + &to_builds, + filter, + &build_config.requested_kinds, + explicit_host_kind, + build_config.mode, + &resolve, + &workspace_resolve, + &resolved_features, + &pkg_set, + &profiles, + interner, + )?, + }; + + 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")?; + } + if ppt.is_none() && 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"); + } + let (std_package_set, std_resolve, std_features) = match ppt { + Some(_) => standard_lib::resolve_std( + ws, + &per_package_target_data, + &build_config.requested_kinds, + crates, + )?, + None => standard_lib::resolve_std( + ws, + &host_target_data, + &build_config.requested_kinds, + crates, + )?, + }; + + pkg_set.add_set(std_package_set); + + Some((std_resolve, std_features)) + } else { + None + }; let mut scrape_units = match rustdoc_scrape_examples { Some(arg) => { @@ -522,20 +673,37 @@ pub fn create_bcx<'a, 'cfg>( let to_builds = pkg_set.get_many(to_build_ids)?; let mode = CompileMode::Docscrape; - generate_targets( - ws, - &to_builds, - &filter, - &build_config.requested_kinds, - explicit_host_kind, - mode, - &resolve, - &workspace_resolve, - &resolved_features, - &pkg_set, - &profiles, - interner, - )? + let generated = match ppt { + Some(_) => generate_targets( + ws, + &to_builds, + &filter, + &explicit_host_kinds, + explicit_host_kind, + mode, + &resolve, + &workspace_resolve, + &resolved_features, + &pkg_set, + &profiles, + interner, + )?, + None => generate_targets( + ws, + &to_builds, + &filter, + &build_config.requested_kinds, + explicit_host_kind, + mode, + &resolve, + &workspace_resolve, + &resolved_features, + &pkg_set, + &profiles, + interner, + )?, + }; + generated } None => Vec::new(), }; @@ -567,20 +735,36 @@ pub fn create_bcx<'a, 'cfg>( Default::default() }; - let mut unit_graph = build_unit_dependencies( - ws, - &pkg_set, - &resolve, - &resolved_features, - std_resolve_features.as_ref(), - &units, - &scrape_units, - &std_roots, - build_config.mode, - &target_data, - &profiles, - interner, - )?; + let mut unit_graph = match ppt { + Some(_) => build_unit_dependencies( + ws, + &pkg_set, + &resolve, + &resolved_features, + std_resolve_features.as_ref(), + &units, + &scrape_units, + &std_roots, + build_config.mode, + &per_package_target_data, + &profiles, + interner, + )?, + None => build_unit_dependencies( + ws, + &pkg_set, + &resolve, + &resolved_features, + std_resolve_features.as_ref(), + &units, + &scrape_units, + &std_roots, + build_config.mode, + &host_target_data, + &profiles, + interner, + )?, + }; // TODO: In theory, Cargo should also dedupe the roots, but I'm uncertain // what heuristics to use in that case. @@ -646,7 +830,7 @@ pub fn create_bcx<'a, 'cfg>( if honor_rust_version { // Remove any pre-release identifiers for easier comparison - let current_version = &target_data.rustc.version; + let current_version = &host_target_data.rustc.version; let untagged_version = semver::Version::new( current_version.major, current_version.minor, @@ -674,17 +858,30 @@ pub fn create_bcx<'a, 'cfg>( } } - let bcx = BuildContext::new( - ws, - pkg_set, - build_config, - profiles, - extra_compiler_args, - target_data, - units, - unit_graph, - scrape_units, - )?; + let bcx = match ppt { + Some(_) => BuildContext::new( + ws, + pkg_set, + build_config, + profiles, + extra_compiler_args, + per_package_target_data, + units, + unit_graph, + scrape_units, + )?, + None => BuildContext::new( + ws, + pkg_set, + build_config, + profiles, + extra_compiler_args, + host_target_data, + units, + unit_graph, + scrape_units, + )?, + }; Ok(bcx) } diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 4380d3f48c1..59141d782b0 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -523,7 +523,7 @@ pub fn install( source_id: SourceId, from_cwd: bool, vers: Option<&str>, - opts: &ops::CompileOptions, + opts: &ops::CompileOptions, // #9451 force: bool, no_track: bool, ) -> CargoResult<()> { diff --git a/src/cargo/ops/cargo_run.rs b/src/cargo/ops/cargo_run.rs index 69bae2c5912..79f378bc4e5 100644 --- a/src/cargo/ops/cargo_run.rs +++ b/src/cargo/ops/cargo_run.rs @@ -9,7 +9,7 @@ use crate::util::CargoResult; pub fn run( ws: &Workspace<'_>, - options: &ops::CompileOptions, + options: &ops::CompileOptions, // #9451 args: &[OsString], ) -> CargoResult<()> { let config = ws.config(); diff --git a/src/cargo/ops/cargo_test.rs b/src/cargo/ops/cargo_test.rs index 04d6b1ac27a..cc4c49ac7cb 100644 --- a/src/cargo/ops/cargo_test.rs +++ b/src/cargo/ops/cargo_test.rs @@ -63,7 +63,7 @@ pub fn run_benches( } fn compile_tests<'a>(ws: &Workspace<'a>, options: &TestOptions) -> CargoResult> { - let mut compilation = ops::compile(ws, &options.compile_opts)?; + let mut compilation = ops::compile(ws, &options.compile_opts)?; // #9451 compilation.tests.sort(); Ok(compilation) } diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 81b379bec3a..1f48dffffcb 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -127,7 +127,7 @@ pub fn fix(ws: &Workspace<'_>, opts: &mut FixOptions) -> CargoResult<()> { // repeating build until there are no more changes to be applied opts.compile_opts.build_config.primary_unit_rustc = Some(wrapper); - ops::compile(ws, &opts.compile_opts)?; + ops::compile(ws, &mut opts.compile_opts)?; // #9451 Ok(()) } diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index e81486f3dd5..cc31182782d 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -4,7 +4,9 @@ pub use self::cargo_clean::{clean, CleanOptions}; pub use self::cargo_compile::{ compile, compile_with_exec, compile_ws, create_bcx, print, resolve_all_features, CompileOptions, }; -pub use self::cargo_compile::{CompileFilter, FilterRule, LibRule, Packages}; +pub use self::cargo_compile::{ + CompileFilter, FilterRule, LibRule, Packages, PerPackageTarget, PerPackageTargetMode, +}; pub use self::cargo_doc::{doc, DocOptions}; pub use self::cargo_fetch::{fetch, FetchOptions}; pub use self::cargo_generate_lockfile::generate_lockfile; diff --git a/tests/build-std/main.rs b/tests/build-std/main.rs index c1355b317ce..1eff39381f2 100644 --- a/tests/build-std/main.rs +++ b/tests/build-std/main.rs @@ -133,10 +133,13 @@ fn cross_custom() { .file( "Cargo.toml", r#" + cargo-features = ["per-package-target"] + [package] name = "foo" version = "0.1.0" - edition = "2018" + edition = "2021" + default-target="custom-target.json" [target.custom-target.dependencies] dep = { path = "dep" } @@ -165,14 +168,27 @@ fn cross_custom() { ) .build(); - p.cargo("build --target custom-target.json -v") - .build_std_arg("core") - .run(); + p.cargo("build -v").build_std_arg("core").run(); } #[cargo_test(build_std)] fn custom_test_framework() { let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["per-package-target"] + + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + default-target="target.json" + + [target.custom-target.dependencies] + dep = { path = "dep" } + "#, + ) .file( "src/lib.rs", r#" @@ -222,7 +238,7 @@ fn custom_test_framework() { paths.insert(0, sysroot_bin); let new_path = env::join_paths(paths).unwrap(); - p.cargo("test --target target.json --no-run -v") + p.cargo("test --no-run -v") .env("PATH", new_path) .build_std_arg("core") .run(); diff --git a/tests/testsuite/standard_lib.rs b/tests/testsuite/standard_lib.rs index 5e2b5719319..83c848090c2 100644 --- a/tests/testsuite/standard_lib.rs +++ b/tests/testsuite/standard_lib.rs @@ -1,180 +1,55 @@ -//! Tests for building the standard library (-Zbuild-std). +//! A test suite for `-Zbuild-std` which is much more expensive than the +//! standard test suite. //! -//! These tests all use a "mock" standard library so that we don't have to -//! rebuild the real one. There is a separate integration test `build-std` -//! which builds the real thing, but that should be avoided if possible. - -use cargo_test_support::registry::{Dependency, Package}; -use cargo_test_support::ProjectBuilder; -use cargo_test_support::{is_nightly, paths, project, rustc_host, Execs}; -use std::path::{Path, PathBuf}; - -struct Setup { - rustc_wrapper: PathBuf, - real_sysroot: String, -} - -fn setup() -> Option { - if !is_nightly() { - // -Zbuild-std is nightly - // We don't want these tests to run on rust-lang/rust. - return None; - } - - if cfg!(all(target_os = "windows", target_env = "gnu")) { - // FIXME: contains object files that we don't handle yet: - // https://github.com/rust-lang/wg-cargo-std-aware/issues/46 - return None; - } - - // 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. - Package::new("registry-dep-using-core", "1.0.0") - .file( - "src/lib.rs", - " - #![no_std] - - #[cfg(feature = \"mockbuild\")] - pub fn custom_api() { - } - - #[cfg(not(feature = \"mockbuild\"))] - pub fn non_sysroot_api() { - core::custom_api(); - } - ", - ) - .add_dep(Dependency::new("rustc-std-workspace-core", "*").optional(true)) - .feature("mockbuild", &["rustc-std-workspace-core"]) - .publish(); - Package::new("registry-dep-using-alloc", "1.0.0") - .file( - "src/lib.rs", - " - #![no_std] - - extern crate alloc; - - #[cfg(feature = \"mockbuild\")] - pub fn custom_api() { - } - - #[cfg(not(feature = \"mockbuild\"))] - pub fn non_sysroot_api() { - core::custom_api(); - alloc::custom_api(); - } - ", - ) - .add_dep(Dependency::new("rustc-std-workspace-core", "*").optional(true)) - .add_dep(Dependency::new("rustc-std-workspace-alloc", "*").optional(true)) - .feature( - "mockbuild", - &["rustc-std-workspace-core", "rustc-std-workspace-alloc"], - ) - .publish(); - Package::new("registry-dep-using-std", "1.0.0") - .file( - "src/lib.rs", - " - #[cfg(feature = \"mockbuild\")] - pub fn custom_api() { - } - - #[cfg(not(feature = \"mockbuild\"))] - pub fn non_sysroot_api() { - std::custom_api(); - } - ", - ) - .add_dep(Dependency::new("rustc-std-workspace-std", "*").optional(true)) - .feature("mockbuild", &["rustc-std-workspace-std"]) - .publish(); - - let p = ProjectBuilder::new(paths::root().join("rustc-wrapper")) - .file( - "src/main.rs", - r#" - use std::process::Command; - use std::env; - fn main() { - let mut args = env::args().skip(1).collect::>(); - - let is_sysroot_crate = env::var_os("RUSTC_BOOTSTRAP").is_some(); - if is_sysroot_crate { - args.push("--sysroot".to_string()); - args.push(env::var("REAL_SYSROOT").unwrap()); - } else if args.iter().any(|arg| arg == "--target") { - // build-std target unit - args.push("--sysroot".to_string()); - args.push("/path/to/nowhere".to_string()); - } else { - // host unit, do not use sysroot - } - - let ret = Command::new(&args[0]).args(&args[1..]).status().unwrap(); - std::process::exit(ret.code().unwrap_or(1)); - } - "#, - ) - .build(); - p.cargo("build").run(); +//! This test suite attempts to perform a full integration test where we +//! actually compile the standard library from source (like the real one) and +//! the various tests associated with that. +//! +//! YOU SHOULD IDEALLY NOT WRITE TESTS HERE. +//! +//! If possible, use `tests/testsuite/standard_lib.rs` instead. That uses a +//! 'mock' sysroot which is much faster to compile. The tests here are +//! extremely intensive and are only intended to run on CI and are theoretically +//! not catching any regressions that `tests/testsuite/standard_lib.rs` isn't +//! already catching. +//! +//! All tests here should use `#[cargo_test(build_std)]` to indicate that +//! boilerplate should be generated to require the nightly toolchain and the +//! `CARGO_RUN_BUILD_STD_TESTS` env var to be set to actually run these tests. +//! Otherwise the tests are skipped. - Some(Setup { - rustc_wrapper: p.bin("foo"), - real_sysroot: paths::sysroot(), - }) -} +use cargo_test_support::*; +use std::env; +use std::path::Path; -fn enable_build_std(e: &mut Execs, setup: &Setup) { - // First up, force Cargo to use our "mock sysroot" which mimics what - // libstd looks like upstream. - let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/testsuite/mock-std"); - e.env("__CARGO_TESTS_ONLY_SRC_ROOT", &root); +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(); - - // We do various shenanigans to ensure our "mock sysroot" actually links - // with the real sysroot, so we don't have to actually recompile std for - // each test. Perform all that logic here, namely: - // - // * RUSTC_WRAPPER - uses our shim executable built above to control rustc - // * REAL_SYSROOT - used by the shim executable to swap out to the real - // sysroot temporarily for some compilations - // * RUST{,DOC}FLAGS - an extra `-L` argument to ensure we can always load - // crates from the sysroot, but only indirectly through other crates. - e.env("RUSTC_WRAPPER", &setup.rustc_wrapper); - e.env("REAL_SYSROOT", &setup.real_sysroot); - let libdir = format!("/lib/rustlib/{}/lib", rustc_host()); - e.env( - "RUSTFLAGS", - format!("-Ldependency={}{}", setup.real_sysroot, libdir), - ); - e.env( - "RUSTDOCFLAGS", - format!("-Ldependency={}{}", setup.real_sysroot, libdir), - ); } // 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 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, setup: &Setup) -> &mut Self { - enable_build_std(self, setup); - self.arg("-Zbuild-std"); + fn build_std(&mut self) -> &mut Self { + enable_build_std(self, None); self } - fn build_std_arg(&mut self, setup: &Setup, arg: &str) -> &mut Self { - enable_build_std(self, setup); - self.arg(format!("-Zbuild-std={}", arg)); + fn build_std_arg(&mut self, arg: &str) -> &mut Self { + enable_build_std(self, Some(arg)); self } @@ -184,25 +59,18 @@ impl BuildStd for Execs { } } -#[cargo_test] +#[cargo_test(build_std)] fn basic() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() .file( "src/main.rs", " fn main() { - std::custom_api(); foo::f(); } #[test] fn smoke_bin_unit() { - std::custom_api(); foo::f(); } ", @@ -217,15 +85,10 @@ fn basic() { /// foo::f(); /// ``` pub fn f() { - core::custom_api(); - std::custom_api(); - alloc::custom_api(); - proc_macro::custom_api(); } #[test] fn smoke_lib_unit() { - std::custom_api(); f(); } ", @@ -235,458 +98,148 @@ fn basic() { " #[test] fn smoke_integration() { - std::custom_api(); foo::f(); } ", ) .build(); - p.cargo("check -v").build_std(&setup).target_host().run(); - p.cargo("build").build_std(&setup).target_host().run(); - p.cargo("run").build_std(&setup).target_host().run(); - p.cargo("test").build_std(&setup).target_host().run(); -} - -#[cargo_test] -fn simple_lib_std() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project().file("src/lib.rs", "").build(); - p.cargo("build -v") - .build_std(&setup) - .target_host() - .with_stderr_contains("[RUNNING] `[..]--crate-name std [..]`") - .run(); - // Check freshness. - p.change_file("src/lib.rs", " "); - p.cargo("build -v") - .build_std(&setup) + p.cargo("check").build_std().target_host().run(); + p.cargo("build") + .build_std() .target_host() - .with_stderr_contains("[FRESH] std[..]") - .run(); -} - -#[cargo_test] -fn simple_bin_std() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project().file("src/main.rs", "fn main() {}").build(); - p.cargo("run -v").build_std(&setup).target_host().run(); -} - -#[cargo_test] -fn lib_nostd() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "src/lib.rs", - r#" - #![no_std] - pub fn foo() { - assert_eq!(u8::MIN, 0); - } - "#, + // Importantly, this should not say [UPDATING] + // There have been multiple bugs where every build triggers and update. + .with_stderr( + "[COMPILING] foo v0.0.1 [..]\n\ + [FINISHED] dev [..]", ) - .build(); - p.cargo("build -v --lib") - .build_std_arg(&setup, "core") - .target_host() - .with_stderr_does_not_contain("[..]libstd[..]") .run(); -} - -#[cargo_test] -fn check_core() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file("src/lib.rs", "#![no_std] fn unused_fn() {}") - .build(); + p.cargo("run").build_std().target_host().run(); + p.cargo("test").build_std().target_host().run(); - p.cargo("check -v") - .build_std_arg(&setup, "core") - .target_host() - .with_stderr_contains("[WARNING] [..]unused_fn[..]`") - .run(); + // Check for hack that removes dylibs. + let deps_dir = Path::new("target") + .join(rustc_host()) + .join("debug") + .join("deps"); + assert!(p.glob(deps_dir.join("*.rlib")).count() > 0); + assert_eq!(p.glob(deps_dir.join("*.dylib")).count(), 0); } -#[cargo_test] -fn depend_same_as_std() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - +#[cargo_test(build_std)] +fn cross_custom() { let p = project() - .file( - "src/lib.rs", - r#" - pub fn f() { - registry_dep_using_core::non_sysroot_api(); - registry_dep_using_alloc::non_sysroot_api(); - registry_dep_using_std::non_sysroot_api(); - } - "#, - ) .file( "Cargo.toml", r#" + cargo-features = ["per-package-target"] + [package] name = "foo" version = "0.1.0" - edition = "2018" + edition = "2021" + default-target="custom-target.json" - [dependencies] - registry-dep-using-core = "1.0" - registry-dep-using-alloc = "1.0" - registry-dep-using-std = "1.0" + [target.custom-target.dependencies] + dep = { path = "dep" } "#, ) - .build(); - - p.cargo("build -v").build_std(&setup).target_host().run(); -} - -#[cargo_test] -fn test() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() .file( "src/lib.rs", - r#" - #[cfg(test)] - mod tests { - #[test] - fn it_works() { - assert_eq!(2 + 2, 4); - } - } - "#, + "#![no_std] pub fn f() -> u32 { dep::answer() }", ) - .build(); - - p.cargo("test -v") - .build_std(&setup) - .target_host() - .with_stdout_contains("test tests::it_works ... ok") - .run(); -} - -#[cargo_test] -fn target_proc_macro() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() + .file("dep/Cargo.toml", &basic_manifest("dep", "0.1.0")) + .file("dep/src/lib.rs", "#![no_std] pub fn answer() -> u32 { 42 }") .file( - "src/lib.rs", + "custom-target.json", r#" - extern crate proc_macro; - pub fn f() { - let _ts = proc_macro::TokenStream::new(); - } + { + "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" + } "#, ) .build(); - p.cargo("build -v").build_std(&setup).target_host().run(); + p.cargo("build -v").build_std_arg("core").run(); } -#[cargo_test] -fn bench() { - let setup = match setup() { - Some(s) => s, - None => return, - }; +#[cargo_test(build_std)] +fn custom_test_framework() { let p = project() .file( - "src/lib.rs", - r#" - #![feature(test)] - extern crate test; - - #[bench] - fn b1(b: &mut test::Bencher) { - b.iter(|| ()) - } - "#, - ) - .build(); - - p.cargo("bench -v").build_std(&setup).target_host().run(); -} - -#[cargo_test] -fn doc() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "src/lib.rs", + "Cargo.toml", r#" - /// Doc - pub fn f() -> Result<(), ()> {Ok(())} - "#, - ) - .build(); + cargo-features = ["per-package-target"] - p.cargo("doc -v").build_std(&setup).target_host().run(); -} + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + default-target="custom.json" -#[cargo_test] -fn check_std() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "src/lib.rs", - " - extern crate core; - extern crate alloc; - extern crate proc_macro; - pub fn f() {} - ", - ) - .file("src/main.rs", "fn main() {}") - .file( - "tests/t1.rs", - r#" - #[test] - fn t1() { - assert_eq!(1, 2); - } + [target.custom-target.dependencies] + dep = { path = "dep" } "#, ) - .build(); - - p.cargo("check -v --all-targets") - .build_std(&setup) - .target_host() - .run(); - p.cargo("check -v --all-targets --profile=test") - .build_std(&setup) - .target_host() - .run(); -} - -#[cargo_test] -fn doctest() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() .file( "src/lib.rs", r#" - /// Doc - /// ``` - /// std::custom_api(); - /// ``` - pub fn f() {} - "#, - ) - .build(); + #![no_std] + #![cfg_attr(test, no_main)] + #![feature(custom_test_frameworks)] + #![test_runner(crate::test_runner)] - p.cargo("test --doc -v -Zdoctest-xcompile") - .build_std(&setup) - .with_stdout_contains("test src/lib.rs - f [..] ... ok") - .target_host() - .run(); -} + pub fn test_runner(_tests: &[&dyn Fn()]) {} -#[cargo_test] -fn no_implicit_alloc() { - // Demonstrate that alloc is not implicitly in scope. - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "src/lib.rs", - r#" - pub fn f() { - let _: Vec = alloc::vec::Vec::new(); - } + #[panic_handler] + fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} + } "#, ) - .build(); - - p.cargo("build -v") - .build_std(&setup) - .target_host() - .with_stderr_contains("[..]use of undeclared [..]`alloc`") - .with_status(101) - .run(); -} - -#[cargo_test] -fn macro_expanded_shadow() { - // This tests a bug caused by the previous use of `--extern` to directly - // load sysroot crates. This necessitated the switch to `--sysroot` to - // retain existing behavior. See - // https://github.com/rust-lang/wg-cargo-std-aware/issues/40 for more - // detail. - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "src/lib.rs", - r#" - macro_rules! a { - () => (extern crate std as alloc;) - } - a!(); - "#, - ) - .build(); - - p.cargo("build -v").build_std(&setup).target_host().run(); -} - -#[cargo_test] -fn ignores_incremental() { - // Incremental is not really needed for std, make sure it is disabled. - // Incremental also tends to have bugs that affect std libraries more than - // any other crate. - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project().file("src/lib.rs", "").build(); - p.cargo("build") - .env("CARGO_INCREMENTAL", "1") - .build_std(&setup) - .target_host() - .run(); - let incremental: Vec<_> = p - .glob(format!("target/{}/debug/incremental/*", rustc_host())) - .map(|e| e.unwrap()) - .collect(); - assert_eq!(incremental.len(), 1); - assert!(incremental[0] - .file_name() - .unwrap() - .to_str() - .unwrap() - .starts_with("foo-")); -} - -#[cargo_test] -fn cargo_config_injects_compiler_builtins() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() .file( - "src/lib.rs", + "target.json", r#" - #![no_std] - pub fn foo() { - assert_eq!(u8::MIN, 0); - } + { + "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" + } "#, ) - .file( - ".cargo/config.toml", - r#" - [unstable] - build-std = ['core'] - "#, - ) - .build(); - let mut build = p.cargo("build -v --lib"); - enable_build_std(&mut build, &setup); - build - .target_host() - .with_stderr_does_not_contain("[..]libstd[..]") - .run(); -} - -#[cargo_test] -fn different_features() { - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "src/lib.rs", - " - pub fn foo() { - std::conditional_function(); - } - ", - ) .build(); - p.cargo("build") - .build_std(&setup) - .arg("-Zbuild-std-features=feature1") - .target_host() - .run(); -} - -#[cargo_test] -fn no_roots() { - // Checks for a bug where it would panic if there are no roots. - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project().file("tests/t1.rs", "").build(); - p.cargo("build") - .build_std(&setup) - .target_host() - .with_stderr_contains("[FINISHED] [..]") - .run(); -} -#[cargo_test] -fn proc_macro_only() { - // Checks for a bug where it would panic if building a proc-macro only - let setup = match setup() { - Some(s) => s, - None => return, - }; - let p = project() - .file( - "Cargo.toml", - r#" - [package] - name = "pm" - version = "0.1.0" + // This is a bit of a hack to use the rust-lld that ships with most toolchains. + let sysroot = paths::sysroot(); + let sysroot = Path::new(&sysroot); + let sysroot_bin = sysroot + .join("lib") + .join("rustlib") + .join(rustc_host()) + .join("bin"); + let path = env::var_os("PATH").unwrap_or_default(); + let mut paths = env::split_paths(&path).collect::>(); + paths.insert(0, sysroot_bin); + let new_path = env::join_paths(paths).unwrap(); - [lib] - proc-macro = true - "#, - ) - .file("src/lib.rs", "") - .build(); - p.cargo("build") - .build_std(&setup) - .target_host() - .with_stderr_contains("[FINISHED] [..]") + p.cargo("test --target target.json --no-run -v") + .env("PATH", new_path) + .build_std_arg("core") .run(); }