diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 1b3337549d2..ce4b96ba3db 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -94,6 +94,7 @@ use crate::util::errors::{CargoResult, VerboseError}; use crate::util::interning::InternedString; use crate::util::machine_message::{self, Message}; use crate::util::toml::TomlDebugInfo; +use crate::util::toml::TomlTrimPaths; use crate::util::{add_path_args, internal, iter_join_onto, profile}; use cargo_util::{paths, ProcessBuilder, ProcessError}; use rustfix::diagnostics::Applicability; @@ -950,6 +951,7 @@ fn build_base_args(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit) incremental, strip, rustflags: profile_rustflags, + trim_paths, .. } = unit.profile.clone(); let test = unit.mode.is_any_test(); @@ -1028,6 +1030,10 @@ fn build_base_args(cx: &Context<'_, '_>, cmd: &mut ProcessBuilder, unit: &Unit) } } + if let Some(trim_paths) = trim_paths { + trim_paths_args(cmd, cx, unit, &trim_paths)?; + } + cmd.args(unit.pkg.manifest().lint_rustflags()); cmd.args(&profile_rustflags); if let Some(args) = cx.bcx.extra_args_for(unit) { @@ -1162,6 +1168,74 @@ fn features_args(unit: &Unit) -> Vec { args } +/// Generates the `--remap-path-scope` and `--remap-path-prefix` for [RFC 3127]. +/// See also unstable feature [`-Ztrim-paths`]. +/// +/// [RFC 3127]: https://rust-lang.github.io/rfcs/3127-trim-paths.html +/// [`-Ztrim-paths`]: https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#profile-trim-paths-option +fn trim_paths_args( + cmd: &mut ProcessBuilder, + cx: &Context<'_, '_>, + unit: &Unit, + trim_paths: &TomlTrimPaths, +) -> CargoResult<()> { + if trim_paths.is_none() { + return Ok(()); + } + + // feature gate was checked during mainfest/config parsing. + cmd.arg("-Zunstable-options"); + cmd.arg(format!("-Zremap-path-scope={trim_paths}")); + + let sysroot_remap = { + let sysroot = &cx.bcx.target_data.info(unit.kind).sysroot; + let mut remap = OsString::from("--remap-path-prefix="); + remap.push(sysroot); + remap.push("/lib/rustlib/src/rust"); // See also `detect_sysroot_src_path()`. + remap.push("="); + remap.push("/rustc/"); + // This remap logic aligns with rustc: + // + if let Some(commit_hash) = cx.bcx.rustc().commit_hash.as_ref() { + remap.push(commit_hash); + } else { + remap.push(cx.bcx.rustc().version.to_string()); + } + remap + }; + cmd.arg(sysroot_remap); + + let package_remap = { + let pkg_root = unit.pkg.root(); + let ws_root = cx.bcx.ws.root(); + let is_local = unit.pkg.package_id().source_id().is_path(); + let mut remap = OsString::from("--remap-path-prefix="); + // Remapped to path relative to workspace root: + // + // * path dependencies under workspace root directory + // + // Remapped to `-` + // + // * registry dependencies + // * git dependencies + // * path dependencies outside workspace root directory + if is_local && pkg_root.strip_prefix(ws_root).is_ok() { + remap.push(ws_root); + remap.push("="); // empty to remap to relative paths. + } else { + remap.push(pkg_root); + remap.push("="); + remap.push(unit.pkg.name()); + remap.push("-"); + remap.push(unit.pkg.version().to_string()); + } + remap + }; + cmd.arg(package_remap); + + Ok(()) +} + /// Generates the `--check-cfg` arguments for the `unit`. /// See unstable feature [`check-cfg`]. /// diff --git a/src/cargo/core/features.rs b/src/cargo/core/features.rs index 224885c955d..c6a95b2eb98 100644 --- a/src/cargo/core/features.rs +++ b/src/cargo/core/features.rs @@ -496,6 +496,9 @@ features! { // Support for 2024 edition. (unstable, edition2024, "", "reference/unstable.html#edition-2024"), + + // Allow setting trim-paths in a profile to control the sanitisation of file paths in build outputs. + (unstable, trim_paths, "", "reference/unstable.html#profile-trim-paths-option"), } pub struct Feature { @@ -755,6 +758,7 @@ unstable_cli_options!( separate_nightlies: bool = (HIDDEN), skip_rustdoc_fingerprint: bool = (HIDDEN), target_applies_to_host: bool = ("Enable the `target-applies-to-host` key in the .cargo/config.toml file"), + trim_paths: bool = ("Enable the `trim-paths` option in profiles"), unstable_options: bool = ("Allow the usage of unstable options"), ); @@ -1089,6 +1093,7 @@ impl CliUnstable { "no-index-update" => self.no_index_update = parse_empty(k, v)?, "panic-abort-tests" => self.panic_abort_tests = parse_empty(k, v)?, "profile-rustflags" => self.profile_rustflags = parse_empty(k, v)?, + "trim-paths" => self.trim_paths = parse_empty(k, v)?, "publish-timeout" => self.publish_timeout = parse_empty(k, v)?, "rustdoc-map" => self.rustdoc_map = parse_empty(k, v)?, "rustdoc-scrape-examples" => self.rustdoc_scrape_examples = parse_empty(k, v)?, diff --git a/src/cargo/core/profiles.rs b/src/cargo/core/profiles.rs index f6ae35fb34a..06a5f3b5784 100644 --- a/src/cargo/core/profiles.rs +++ b/src/cargo/core/profiles.rs @@ -24,8 +24,11 @@ use crate::core::compiler::{CompileKind, CompileTarget, Unit}; use crate::core::dependency::Artifact; use crate::core::resolver::features::FeaturesFor; +use crate::core::Feature; use crate::core::{PackageId, PackageIdSpec, Resolve, Shell, Target, Workspace}; use crate::util::interning::InternedString; +use crate::util::toml::TomlTrimPaths; +use crate::util::toml::TomlTrimPathsValue; use crate::util::toml::{ ProfilePackageSpec, StringOrBool, TomlDebugInfo, TomlProfile, TomlProfiles, }; @@ -80,7 +83,9 @@ impl Profiles { rustc_host, }; - Self::add_root_profiles(&mut profile_makers, &profiles); + let trim_paths_enabled = ws.unstable_features().is_enabled(Feature::trim_paths()) + || config.cli_unstable().trim_paths; + Self::add_root_profiles(&mut profile_makers, &profiles, trim_paths_enabled); // Merge with predefined profiles. use std::collections::btree_map::Entry; @@ -123,6 +128,7 @@ impl Profiles { fn add_root_profiles( profile_makers: &mut Profiles, profiles: &BTreeMap, + trim_paths_enabled: bool, ) { profile_makers.by_name.insert( InternedString::new("dev"), @@ -131,7 +137,10 @@ impl Profiles { profile_makers.by_name.insert( InternedString::new("release"), - ProfileMaker::new(Profile::default_release(), profiles.get("release").cloned()), + ProfileMaker::new( + Profile::default_release(trim_paths_enabled), + profiles.get("release").cloned(), + ), ); } @@ -556,6 +565,9 @@ fn merge_profile(profile: &mut Profile, toml: &TomlProfile) { if let Some(flags) = &toml.rustflags { profile.rustflags = flags.iter().map(InternedString::from).collect(); } + if let Some(trim_paths) = &toml.trim_paths { + profile.trim_paths = Some(trim_paths.clone()); + } profile.strip = match toml.strip { Some(StringOrBool::Bool(true)) => Strip::Named(InternedString::new("symbols")), None | Some(StringOrBool::Bool(false)) => Strip::None, @@ -599,6 +611,9 @@ pub struct Profile { #[serde(skip_serializing_if = "Vec::is_empty")] // remove when `rustflags` is stablized // Note that `rustflags` is used for the cargo-feature `profile_rustflags` pub rustflags: Vec, + // remove when `-Ztrim-paths` is stablized + #[serde(skip_serializing_if = "Option::is_none")] + pub trim_paths: Option, } impl Default for Profile { @@ -619,6 +634,7 @@ impl Default for Profile { panic: PanicStrategy::Unwind, strip: Strip::None, rustflags: vec![], + trim_paths: None, } } } @@ -628,7 +644,7 @@ compact_debug! { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let (default, default_name) = match self.name.as_str() { "dev" => (Profile::default_dev(), "default_dev()"), - "release" => (Profile::default_release(), "default_release()"), + "release" => (Profile::default_release(false), "default_release()"), _ => (Profile::default(), "default()"), }; [debug_the_fields( @@ -647,6 +663,7 @@ compact_debug! { panic strip rustflags + trim_paths )] } } @@ -688,11 +705,13 @@ impl Profile { } /// Returns a built-in `release` profile. - fn default_release() -> Profile { + fn default_release(trim_paths_enabled: bool) -> Profile { + let trim_paths = trim_paths_enabled.then(|| TomlTrimPathsValue::Object.into()); Profile { name: InternedString::new("release"), root: ProfileRoot::Release, opt_level: InternedString::new("3"), + trim_paths, ..Profile::default() } } @@ -713,6 +732,7 @@ impl Profile { self.rpath, (self.incremental, self.panic, self.strip), &self.rustflags, + &self.trim_paths, ) } } diff --git a/src/cargo/util/rustc.rs b/src/cargo/util/rustc.rs index d1bb3981dc4..f51580f29b4 100644 --- a/src/cargo/util/rustc.rs +++ b/src/cargo/util/rustc.rs @@ -28,6 +28,8 @@ pub struct Rustc { pub version: semver::Version, /// The host triple (arch-platform-OS), this comes from verbose_version. pub host: InternedString, + /// The rustc full commit hash, this comes from `verbose_version`. + pub commit_hash: Option, cache: Mutex, } @@ -80,6 +82,17 @@ impl Rustc { verbose_version ) })?; + let commit_hash = extract("commit-hash: ").ok().map(|hash| { + debug_assert!( + hash.chars().all(|ch| ch.is_ascii_hexdigit()), + "commit hash must be a hex string" + ); + debug_assert!( + hash.len() == 40 || hash.len() == 64, + "hex string must be generated from sha1 or sha256" + ); + hash.to_string() + }); Ok(Rustc { path, @@ -88,6 +101,7 @@ impl Rustc { verbose_version, version, host, + commit_hash, cache: Mutex::new(cache), }) } diff --git a/src/cargo/util/toml/mod.rs b/src/cargo/util/toml/mod.rs index 053c0646b35..a9881497d8d 100644 --- a/src/cargo/util/toml/mod.rs +++ b/src/cargo/util/toml/mod.rs @@ -2692,6 +2692,8 @@ pub struct TomlProfile { // requires all non-tables to be listed first. pub package: Option>, pub build_override: Option>, + /// Unstable feature `-Ztrim-paths`. + pub trim_paths: Option, } impl TomlProfile { @@ -2892,6 +2894,15 @@ impl TomlProfile { _ => {} } } + if self.trim_paths.is_some() { + match ( + features.require(Feature::trim_paths()), + cli_unstable.trim_paths, + ) { + (Err(e), false) => return Err(e), + _ => {} + } + } Ok(()) } @@ -3167,6 +3178,122 @@ impl<'de> de::Deserialize<'de> for TomlDebugInfo { } } +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize)] +#[serde(untagged, rename_all = "kebab-case")] +pub enum TomlTrimPaths { + Values(Vec), + All, +} + +impl TomlTrimPaths { + pub fn none() -> Self { + TomlTrimPaths::Values(Vec::new()) + } + + pub fn is_none(&self) -> bool { + match self { + TomlTrimPaths::Values(v) => v.is_empty(), + TomlTrimPaths::All => false, + } + } +} + +impl<'de> de::Deserialize<'de> for TomlTrimPaths { + fn deserialize(d: D) -> Result + where + D: de::Deserializer<'de>, + { + use serde::de::Error as _; + let expecting = r#"a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options"#; + UntaggedEnumVisitor::new() + .expecting(expecting) + .bool(|value| { + Ok(if value { + TomlTrimPaths::All + } else { + TomlTrimPaths::none() + }) + }) + .string(|v| match v { + "none" => Ok(TomlTrimPaths::none()), + "all" => Ok(TomlTrimPaths::All), + v => { + let d = v.into_deserializer(); + let err = |_: D::Error| { + serde_untagged::de::Error::custom(format!("expected {expecting}")) + }; + TomlTrimPathsValue::deserialize(d) + .map_err(err) + .map(|v| v.into()) + } + }) + .seq(|seq| { + let seq: Vec = seq.deserialize()?; + let seq: Vec<_> = seq + .into_iter() + .map(|s| TomlTrimPathsValue::deserialize(s.into_deserializer())) + .collect::>()?; + Ok(seq.into()) + }) + .deserialize(d) + } +} + +impl fmt::Display for TomlTrimPaths { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TomlTrimPaths::All => write!(f, "all"), + TomlTrimPaths::Values(v) if v.is_empty() => write!(f, "none"), + TomlTrimPaths::Values(v) => { + let mut iter = v.iter(); + if let Some(value) = iter.next() { + write!(f, "{value}")?; + } + for value in iter { + write!(f, ",{value}")?; + } + Ok(()) + } + } + } +} + +impl From for TomlTrimPaths { + fn from(value: TomlTrimPathsValue) -> Self { + TomlTrimPaths::Values(vec![value]) + } +} + +impl From> for TomlTrimPaths { + fn from(value: Vec) -> Self { + TomlTrimPaths::Values(value) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum TomlTrimPathsValue { + Diagnostics, + Macro, + Object, +} + +impl TomlTrimPathsValue { + pub fn as_str(&self) -> &'static str { + match self { + TomlTrimPathsValue::Diagnostics => "diagnostics", + TomlTrimPathsValue::Macro => "macro", + TomlTrimPathsValue::Object => "object", + } + } +} + +impl fmt::Display for TomlTrimPathsValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + type TomlLibTarget = TomlTarget; type TomlBinTarget = TomlTarget; type TomlExampleTarget = TomlTarget; diff --git a/src/doc/src/reference/unstable.md b/src/doc/src/reference/unstable.md index 12a56d72298..67144807fe2 100644 --- a/src/doc/src/reference/unstable.md +++ b/src/doc/src/reference/unstable.md @@ -94,6 +94,7 @@ For the latest nightly, see the [nightly version] of this page. * [per-package-target](#per-package-target) --- Sets the `--target` to use for each individual package. * [artifact dependencies](#artifact-dependencies) --- Allow build artifacts to be included into other build artifacts and build them for different targets. * [Edition 2024](#edition-2024) — Adds support for the 2024 Edition. + * [Profile `trim-paths` option](#profile-trim-paths-option) --- Control the sanitisation of file paths in build outputs. * Information and metadata * [Build-plan](#build-plan) --- Emits JSON information on which commands will be run. * [unit-graph](#unit-graph) --- Emits JSON for Cargo's internal graph structure. @@ -1286,6 +1287,89 @@ edition that may break your build. [edition]: ../../edition-guide/index.html +## Profile `trim-paths` option + +* Tracking Issue: [rust-lang/cargo#12137](https://github.com/rust-lang/cargo/issues/12137) +* Tracking Rustc Issue: [rust-lang/rust#111540](https://github.com/rust-lang/rust/issues/111540) + +This adds a new profile setting to control how paths are sanitised in the resulting binary. +This can be enabled like so: + +```toml +cargo-features = ["trim-paths"] + +[package] +# ... + +[profile.release] +trim-paths = ["diagnostics", "object"] +``` + +To set this in a profile in Cargo configuration, +you need to use either `-Z trim-paths` or `[unstable]` table to enable it. +For example, + +```toml +# .cargo/config.toml +[unstable] +trim-paths = true + +[profile.release] +trim-paths = ["diagnostics", "object"] +``` + +### Documentation updates + +#### trim-paths + +*as a new ["Profiles settings" entry](./profiles.html#profile-settings)* + +`trim-paths` is a profile setting which enables and controls the sanitization of file paths in build outputs. +It takes the following values: + +- `"none"` and `false` --- disable path sanitization +- `"macro"` --- sanitize paths in the expansion of `std::file!()` macro. + This is where paths in embedded panic messages come from +- `"diagnostics"` --- sanitize paths in printed compiler diagnostics +- `"object"` --- sanitize paths in compiled executables or libraries +- `"all"` and `true` --- sanitize paths in all possible locations + +It also takes an array with the combinations of `"macro"`, `"diagnostics"`, and `"object"`. + +It is defaulted to `none` for the `dev` profile, and `object` for the `release` profile. +You can manually override it by specifying this option in `Cargo.toml`: + +```toml +[profile.dev] +trim-paths = "all" + +[profile.release] +trim-paths = ["object", "diagnostics"] +``` + +The default `release` profile setting (`object`) sanitizes only the paths in emitted executable or library files. +It always affects paths from macros such as panic messages, and in debug information only if they will be embedded together with the binary +(the default on platforms with ELF binaries, such as Linux and windows-gnu), +but will not touch them if they are in separate files (the default on Windows MSVC and macOS). +But the paths to these separate files are sanitized. + +If `trim-paths` is not `none` or `false`, then the following paths are sanitized if they appear in a selected scope: + +1. Path to the source files of the standard and core library (sysroot) will begin with `/rustc/[rustc commit hash]`, + e.g. `/home/username/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs` -> + `/rustc/fe72845f7bb6a77b9e671e6a4f32fe714962cec4/library/core/src/result.rs` +2. Path to the current package will be stripped, relatively to the current workspace root, e.g. `/home/username/crate/src/lib.rs` -> `src/lib.rs`. +3. Path to dependency packages will be replaced with `[package name]-[version]`. E.g. `/home/username/deps/foo/src/lib.rs` -> `foo-0.1.0/src/lib.rs` + +When a path to the source files of the standard and core library is *not* in scope for sanitization, +the emitted path will depend on if `rust-src` component is present. +If it is, then some paths will point to the copy of the source files on your file system; +if it isn't, then they will show up as `/rustc/[rustc commit hash]/library/...` +(just like when it is selected for sanitization). +Paths to all other source files will not be affected. + +This will not affect any hard-coded paths in the source code, such as in strings. + # Stabilized and removed features ## Compile progress diff --git a/tests/testsuite/bad_config.rs b/tests/testsuite/bad_config.rs index 82da880ea07..8c86b06c01d 100644 --- a/tests/testsuite/bad_config.rs +++ b/tests/testsuite/bad_config.rs @@ -1664,3 +1664,37 @@ note: Sources are not allowed to be defined multiple times. ) .run(); } + +#[cargo_test] +fn bad_trim_paths() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.0" + + [profile.dev] + trim-paths = "split-debuginfo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check -Ztrim-paths") + .masquerade_as_nightly_cargo(&["trim-paths"]) + .with_status(101) + .with_stderr( + r#"error: failed to parse manifest at `[..]` + +Caused by: + TOML parse error at line 7, column 30 + | + 7 | trim-paths = "split-debuginfo" + | ^^^^^^^^^^^^^^^^^ + expected a boolean, "none", "diagnostics", "macro", "object", "all", or an array with these options +"#, + ) + .run(); +} diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs index f0fdb03adbb..22d6a2b2f97 100644 --- a/tests/testsuite/config.rs +++ b/tests/testsuite/config.rs @@ -2,6 +2,8 @@ use cargo::core::{PackageIdSpec, Shell}; use cargo::util::config::{self, Config, Definition, JobsConfig, SslVersionConfig, StringList}; +use cargo::util::toml::TomlTrimPaths; +use cargo::util::toml::TomlTrimPathsValue; use cargo::util::toml::{self as cargo_toml, TomlDebugInfo, VecStringOrBool as VSOB}; use cargo::CargoResult; use cargo_test_support::compare; @@ -1540,6 +1542,7 @@ fn all_profile_options() { package: None, build_override: None, rustflags: None, + trim_paths: None, }; let mut overrides = BTreeMap::new(); let key = cargo_toml::ProfilePackageSpec::Spec(PackageIdSpec::parse("foo").unwrap()); @@ -1727,3 +1730,63 @@ jobs = 2 JobsConfig::Integer(v) => assert_eq!(v, 2), } } + +#[cargo_test] +fn trim_paths_parsing() { + let config = ConfigBuilder::new().build(); + let p: cargo_toml::TomlProfile = config.get("profile.dev").unwrap(); + assert_eq!(p.trim_paths, None); + + let test_cases = [ + (TomlTrimPathsValue::Diagnostics.into(), "diagnostics"), + (TomlTrimPathsValue::Macro.into(), "macro"), + (TomlTrimPathsValue::Object.into(), "object"), + ]; + for (expected, val) in test_cases { + // env + let config = ConfigBuilder::new() + .env("CARGO_PROFILE_DEV_TRIM_PATHS", val) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + + // config.toml + let config = ConfigBuilder::new() + .config_arg(format!("profile.dev.trim-paths='{val}'")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + } + + let test_cases = [(TomlTrimPaths::none(), false), (TomlTrimPaths::All, true)]; + + for (expected, val) in test_cases { + // env + let config = ConfigBuilder::new() + .env("CARGO_PROFILE_DEV_TRIM_PATHS", format!("{val}")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + + // config.toml + let config = ConfigBuilder::new() + .config_arg(format!("profile.dev.trim-paths={val}")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); + } + + let expected = vec![ + TomlTrimPathsValue::Diagnostics, + TomlTrimPathsValue::Macro, + TomlTrimPathsValue::Object, + ] + .into(); + let val = r#"["diagnostics", "macro", "object"]"#; + // config.toml + let config = ConfigBuilder::new() + .config_arg(format!("profile.dev.trim-paths={val}")) + .build(); + let trim_paths: TomlTrimPaths = config.get("profile.dev.trim-paths").unwrap(); + assert_eq!(trim_paths, expected, "failed to parse {val}"); +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index d31255d0ae7..e2ff2d9a95d 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -138,6 +138,7 @@ mod profile_config; mod profile_custom; mod profile_overrides; mod profile_targets; +mod profile_trim_paths; mod profiles; mod progress; mod pub_priv; diff --git a/tests/testsuite/profile_trim_paths.rs b/tests/testsuite/profile_trim_paths.rs new file mode 100644 index 00000000000..b0b8d9577a4 --- /dev/null +++ b/tests/testsuite/profile_trim_paths.rs @@ -0,0 +1,513 @@ +//! Tests for `-Ztrim-paths`. + +use cargo_test_support::basic_manifest; +use cargo_test_support::git; +use cargo_test_support::paths; +use cargo_test_support::project; +use cargo_test_support::registry::Package; + +#[cargo_test] +fn gated_manifest() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = "macro" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_status(101) + .with_stderr_contains( + "\ +[ERROR] failed to parse manifest at `[CWD]/Cargo.toml` + +Caused by: + feature `trim-paths` is required", + ) + .run(); +} + +#[cargo_test] +fn gated_config_toml() { + let p = project() + .file( + ".cargo/config.toml", + r#" + [profile.dev] + trim-paths = "macro" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("check") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_status(101) + .with_stderr_contains( + "\ +[ERROR] config profile `dev` is not valid (defined in `[CWD]/.cargo/config.toml`) + +Caused by: + feature `trim-paths` is required", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn release_profile_default_to_object() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build --release --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] release [..]", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn one_option() { + let build = |option| { + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = "{option}" + "# + ), + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build -v -Ztrim-paths") + }; + + for option in ["macro", "diagnostics", "object", "all"] { + build(option) + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr(&format!( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope={option} \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..]", + )) + .run(); + } + build("none") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr_does_not_contain("[..]-Zremap-path-scope=[..]") + .with_stderr_does_not_contain("[..]--remap-path-prefix=[..]") + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn multiple_options() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [profile.dev] + trim-paths = ["diagnostics", "macro", "object"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("build --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr( + "\ +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=diagnostics,macro,object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..]", + ) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn registry_dependency() { + Package::new("bar", "0.0.1") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + + [profile.dev] + trim-paths = "object" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + let registry_src = paths::home().join(".cargo/registry/src"); + let pkg_remap = format!("{}/[..]/bar-0.0.1=bar-0.0.1", registry_src.display()); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("bar-0.0.1/src/lib.rs") + .with_stderr(&format!( + "\ +[UPDATING] [..] +[DOWNLOADING] crates ... +[DOWNLOADED] bar v0.0.1 ([..]) +[COMPILING] bar v0.0.1 +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn git_dependency() { + let git_project = git::new("bar", |project| { + project + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + }); + let url = git_project.url(); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = {{ git = "{url}" }} + + [profile.dev] + trim-paths = "object" + "# + ), + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + let git_checkouts_src = paths::home().join(".cargo/git/checkouts"); + let pkg_remap = format!("{}/bar-[..]/[..]=bar-0.0.1", git_checkouts_src.display()); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("bar-0.0.1/src/lib.rs") + .with_stderr(&format!( + "\ +[UPDATING] git repository `{url}` +[COMPILING] bar v0.0.1 ({url}[..]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn path_dependency() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "cocktail-bar" } + + [profile.dev] + trim-paths = "object" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .file("cocktail-bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file( + "cocktail-bar/src/lib.rs", + r#"pub fn f() { println!("{}", file!()); }"#, + ) + .build(); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("cocktail-bar/src/lib.rs") + .with_stderr(&format!( + "\ +[COMPILING] bar v0.0.1 ([..]/cocktail-bar) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn path_dependency_outside_workspace() { + let bar = project() + .at("bar") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + .build(); + let bar_path = bar.url().to_file_path().unwrap(); + let bar_path = bar_path.display(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = { path = "../bar" } + + [profile.dev] + trim-paths = "object" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + p.cargo("run --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stdout("bar-0.0.1/src/lib.rs") + .with_stderr(&format!( + "\ +[COMPILING] bar v0.0.1 ([..]/bar) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={bar_path}=bar-0.0.1 [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..] +[RUNNING] `target/debug/foo[EXE]`" + )) + .run(); +} + +#[cargo_test(nightly, reason = "-Zremap-path-scope is unstable")] +fn diagnostics_works() { + Package::new("bar", "0.0.1") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { let unused = 0; }"#) + .publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + + [profile.dev] + trim-paths = "diagnostics" + "#, + ) + .file("src/lib.rs", "") + .build(); + + let registry_src = paths::home().join(".cargo/registry/src"); + let registry_src = registry_src.display(); + let pkg_remap = format!("{registry_src}/[..]/bar-0.0.1=bar-0.0.1"); + + p.cargo("build -vv -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr_line_without( + &["[..]bar-0.0.1/src/lib.rs:1[..]"], + &[&format!("{registry_src}")], + ) + .with_stderr_contains("[..]unused_variables[..]") + .with_stderr_contains(&format!( + "\ +[RUNNING] [..]rustc [..]\ + -Zremap-path-scope=diagnostics \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..]", + )) + .with_stderr_contains( + "\ +[RUNNING] [..]rustc [..]\ + -Zremap-path-scope=diagnostics \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..]", + ) + .run(); +} + +#[cfg(target_os = "linux")] +#[cargo_test(requires_readelf, nightly, reason = "-Zremap-path-scope is unstable")] +fn object_works() { + use std::os::unix::ffi::OsStrExt; + + let run_readelf = |path| { + std::process::Command::new("readelf") + .arg("-wi") + .arg(path) + .output() + .expect("readelf works") + }; + + let registry_src = paths::home().join(".cargo/registry/src"); + let pkg_remap = format!("{}/[..]/bar-0.0.1=bar-0.0.1", registry_src.display()); + let rust_src = "/lib/rustc/src/rust".as_bytes(); + let registry_src_bytes = registry_src.as_os_str().as_bytes(); + + Package::new("bar", "0.0.1") + .file("Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("src/lib.rs", r#"pub fn f() { println!("{}", file!()); }"#) + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + "#, + ) + .file("src/main.rs", "fn main() { bar::f(); }") + .build(); + + let pkg_root = p.root(); + let pkg_root = pkg_root.as_os_str().as_bytes(); + + p.cargo("build").run(); + + let bin_path = p.bin("foo"); + assert!(bin_path.is_file()); + let stdout = run_readelf(bin_path).stdout; + // TODO: re-enable this check when rustc bootstrap disables remapping + // + // assert!(memchr::memmem::find(&stdout, rust_src).is_some()); + assert!(memchr::memmem::find(&stdout, registry_src_bytes).is_some()); + assert!(memchr::memmem::find(&stdout, pkg_root).is_some()); + + p.cargo("clean").run(); + + p.change_file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + [dependencies] + bar = "0.0.1" + + [profile.dev] + trim-paths = "object" + "#, + ); + + p.cargo("build --verbose -Ztrim-paths") + .masquerade_as_nightly_cargo(&["-Ztrim-paths"]) + .with_stderr(&format!( + "\ +[COMPILING] bar v0.0.1 +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix={pkg_remap} [..] +[COMPILING] foo v0.0.1 ([CWD]) +[RUNNING] `rustc [..]\ + -Zremap-path-scope=object \ + --remap-path-prefix=[..]/lib/rustlib/src/rust=/rustc/[..] \ + --remap-path-prefix=[CWD]= [..] +[FINISHED] dev [..]", + )) + .run(); + + let bin_path = p.bin("foo"); + assert!(bin_path.is_file()); + let stdout = run_readelf(bin_path).stdout; + assert!(memchr::memmem::find(&stdout, rust_src).is_none()); + assert!(memchr::memmem::find(&stdout, registry_src_bytes).is_none()); + assert!(memchr::memmem::find(&stdout, pkg_root).is_none()); +}