diff --git a/src/cargo/core/compiler/mod.rs b/src/cargo/core/compiler/mod.rs index 7ac3842f4df..1a2d686fef1 100644 --- a/src/cargo/core/compiler/mod.rs +++ b/src/cargo/core/compiler/mod.rs @@ -943,6 +943,15 @@ fn build_base_args( .env("RUSTC_BOOTSTRAP", "1"); } + // If the target is using the MSVC toolchain, then pass any NatVis files to it. + let target_config = bcx.target_data.target_config(unit.kind); + let is_msvc = target_config.triple.ends_with("-msvc"); + if is_msvc { + for natvis_file in unit.pkg.manifest().natvis_files().iter() { + cmd.arg(format!("-Clink-arg=/natvis:{}", natvis_file)); + } + } + // Add `CARGO_BIN_` environment variables for building tests. if unit.target.is_test() || unit.target.is_bench() { for bin_target in unit diff --git a/src/cargo/core/manifest.rs b/src/cargo/core/manifest.rs index f6a7323d25f..c05761744d6 100644 --- a/src/cargo/core/manifest.rs +++ b/src/cargo/core/manifest.rs @@ -50,6 +50,7 @@ pub struct Manifest { default_run: Option, metabuild: Option>, resolve_behavior: Option, + natvis_files: Vec, } /// When parsing `Cargo.toml`, some warnings should silenced @@ -384,6 +385,7 @@ impl Manifest { original: Rc, metabuild: Option>, resolve_behavior: Option, + natvis_files: Vec, ) -> Manifest { Manifest { summary, @@ -407,6 +409,7 @@ impl Manifest { publish_lockfile, metabuild, resolve_behavior, + natvis_files, } } @@ -539,6 +542,10 @@ impl Manifest { .join(".metabuild") .join(format!("metabuild-{}-{}.rs", self.name(), hash)) } + + pub fn natvis_files(&self) -> &[String] { + &self.natvis_files + } } impl VirtualManifest { diff --git a/src/cargo/core/package.rs b/src/cargo/core/package.rs index 88bc43c7311..2416aa0bf60 100644 --- a/src/cargo/core/package.rs +++ b/src/cargo/core/package.rs @@ -102,6 +102,8 @@ pub struct SerializedPackage { links: Option, #[serde(skip_serializing_if = "Option::is_none")] metabuild: Option>, + #[serde(skip_serializing_if = "Vec::is_empty")] + natvis_files: Vec, } impl Package { @@ -259,8 +261,13 @@ impl Package { links: self.manifest().links().map(|s| s.to_owned()), metabuild: self.manifest().metabuild().cloned(), publish: self.publish().as_ref().cloned(), + natvis_files: self.natvis_files().to_vec(), } } + + pub fn natvis_files(&self) -> &[String] { + self.inner.manifest.natvis_files() + } } impl fmt::Display for Package { diff --git a/src/cargo/util/config/target.rs b/src/cargo/util/config/target.rs index 13b77e262d0..f6064e15830 100644 --- a/src/cargo/util/config/target.rs +++ b/src/cargo/util/config/target.rs @@ -34,6 +34,7 @@ pub struct TargetConfig { /// running its build script and instead use the given output from the /// config file. pub links_overrides: BTreeMap, + pub triple: String, } /// Loads all of the `target.'cfg()'` tables. @@ -85,6 +86,7 @@ pub(super) fn load_target_triple(config: &Config, triple: &str) -> CargoResult, metadata: Option, resolver: Option, + #[serde(rename = "natvis-files")] + natvis_files: Option>, } #[derive(Debug, Deserialize, Serialize)] @@ -867,7 +869,7 @@ struct Context<'a, 'b> { } impl TomlManifest { - /// Prepares the manfiest for publishing. + /// Prepares the manifest for publishing. // - Path and git components of dependency specifications are removed. // - License path is updated to point within the package. pub fn prepare_for_publish( @@ -1297,6 +1299,15 @@ impl TomlManifest { } } + // Handle NatVis files. If the TomlManifest specifies a set of NatVis files, then we use + // exactly that set and we do not inspect the filesystem. (We do require that the paths + // specified in the manifest be relative paths.) If the manifest does not specify any + // NatVis files, then we look for `natvis/*.natvis` files. + let natvis_files = resolve_natvis_files( + project.natvis_files.as_ref().map(|s| s.as_slice()), + package_root, + )?; + let custom_metadata = project.metadata.clone(); let mut manifest = Manifest::new( summary, @@ -1319,6 +1330,7 @@ impl TomlManifest { Rc::clone(me), project.metabuild.clone().map(|sov| sov.0), resolve_behavior, + natvis_files, ); if project.license_file.is_some() && project.license.is_some() { manifest.warnings_mut().add_warning( @@ -1892,3 +1904,57 @@ impl fmt::Debug for PathValue { self.0.fmt(f) } } + +fn resolve_natvis_files( + toml_natvis_files: Option<&[String]>, + package_root: &Path, +) -> CargoResult> { + if let Some(toml_natvis_files) = toml_natvis_files { + let mut natvis_files = Vec::with_capacity(toml_natvis_files.len()); + for toml_natvis_file in toml_natvis_files.iter() { + let natvis_file_path = Path::new(toml_natvis_file); + if !natvis_file_path.is_relative() { + bail!( + "`natvis-files` contains absolute path `{}`; \ + all paths in `natvis-files` are required to be relative paths.", + toml_natvis_file + ); + } + natvis_files.push( + package_root + .join(natvis_file_path) + .to_str() + .unwrap() + .to_string(), + ); + } + Ok(natvis_files) + } else { + // The manifest file did not specify any natvis files. + // By convention, we look for `natvis\*.natvis` files. + if let Ok(nv) = find_natvis_files(package_root) { + Ok(nv) + } else { + Ok(Vec::new()) + } + } +} + +fn find_natvis_files(package_root: &Path) -> std::io::Result> { + use std::ffi::OsStr; + + let mut natvis_files = Vec::new(); + let natvis_dir = package_root.join("natvis"); + for entry in std::fs::read_dir(&natvis_dir)? { + let entry = entry?; + let filename = PathBuf::from(entry.file_name().to_str().unwrap()); + if filename.extension() == Some(OsStr::new("natvis")) { + if let Ok(md) = entry.metadata() { + if md.is_file() { + natvis_files.push(entry.path().to_str().unwrap().to_string()); + } + } + } + } + Ok(natvis_files) +} diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs index 8af5858b373..413bb433d11 100644 --- a/tests/testsuite/main.rs +++ b/tests/testsuite/main.rs @@ -75,6 +75,7 @@ mod metabuild; mod metadata; mod minimal_versions; mod multitarget; +mod natvis; mod net_config; mod new; mod offline; diff --git a/tests/testsuite/natvis.rs b/tests/testsuite/natvis.rs new file mode 100644 index 00000000000..a9c51a29589 --- /dev/null +++ b/tests/testsuite/natvis.rs @@ -0,0 +1,243 @@ +//! Tests for NatVis support. +//! +//! Currently, there is no way to test for the presence (or absence) +//! of a specific item in a JSON array, so these tests verify all of +//! the arguments in the corresponding `rustc` calls. That's fragile. +//! Ideally, we would be able to test for the `-Clink-args=...` args +//! without caring about any other args. + +use cargo_test_support::{project, rustc_host}; + +const NATVIS_CONTENT: &str = r#" + + + +"#; + +fn is_natvis_supported() -> bool { + rustc_host().ends_with("-msvc") +} + +/// Tests a project that contains a single NatVis file. +/// The file is discovered by Cargo, since it is in the `/natvis` directory, +/// and does not need to be explicitly specified in the manifest. +#[cargo_test] +fn natvis_autodiscovery() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "natvis_autodiscovery" + version = "0.0.1" + edition = "2018" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("natvis/types.natvis", NATVIS_CONTENT) + .build(); + + let mut execs = p.cargo("build --build-plan -Zunstable-options"); + execs.masquerade_as_nightly_cargo(); + + if is_natvis_supported() { + execs.with_json( + r#" + { + "inputs": [ + "[..]/foo/Cargo.toml" + ], + "invocations": [ + { + "args": [ + "--crate-name", + "natvis_autodiscovery", + "--edition=2018", + "src/main.rs", + "--error-format=json", + "--json=[..]", + "--crate-type", + "bin", + "--emit=[..]", + "-C", + "embed-bitcode=[..]", + "-C", + "debuginfo=[..]", + "-C", + "metadata=[..]", + "--out-dir", + "[..]", + "-Clink-arg=/natvis:[..]/foo/natvis/types.natvis", + "-L", + "dependency=[..]" + ], + "cwd": "[..]/cit/[..]/foo", + "deps": [], + "env": "{...}", + "kind": null, + "links": "{...}", + "outputs": "{...}", + "package_name": "natvis_autodiscovery", + "package_version": "0.0.1", + "program": "rustc", + "target_kind": ["bin"], + "compile_mode": "build" + } + ] + } + "#, + ); + } + + execs.run(); +} + +/// Tests a project that contains a single NatVis file, which is explicitly +/// specified in the manifest file. Because it is explicitly specified, it +/// does not have to be in the `/natvis` subdirectory. +#[cargo_test] +fn natvis_explicit() { + if !is_natvis_supported() { + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "natvis_explicit" + version = "0.0.1" + edition = "2018" + natvis-files = ["types.natvis"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("types.natvis", NATVIS_CONTENT) + .build(); + + let mut execs = p.cargo("build --build-plan -Zunstable-options"); + execs.masquerade_as_nightly_cargo(); + + if is_natvis_supported() { + execs.with_json( + r#" + { + "inputs": [ + "[..]/foo/Cargo.toml" + ], + "invocations": [ + { + "args": [ + "--crate-name", + "natvis_explicit", + "--edition=2018", + "src/main.rs", + "--error-format=json", + "--json=[..]", + "--crate-type", + "bin", + "--emit=[..]", + "-C", + "embed-bitcode=[..]", + "-C", + "debuginfo=[..]", + "-C", + "metadata=[..]", + "--out-dir", + "[..]", + "-Clink-arg=/natvis:[..]/foo/types.natvis", + "-L", + "dependency=[..]" + ], + "cwd": "[..]/cit/[..]/foo", + "deps": [], + "env": "{...}", + "kind": null, + "links": "{...}", + "outputs": "{...}", + "package_name": "natvis_explicit", + "package_version": "0.0.1", + "program": "rustc", + "target_kind": ["bin"], + "compile_mode": "build" + } + ] + } + "#, + ); + } + + execs.run(); +} + +/// Tests a project that has a file in the `/natvis` directory, but which has +/// been disabled in the manifest. This is analogous to specifying `autobenches = false`. +#[cargo_test] +fn natvis_disabled() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "natvis_disabled" + version = "0.0.1" + edition = "2018" + natvis-files = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("natvis/types.natvis", NATVIS_CONTENT) + .build(); + + let mut execs = p.cargo("build --build-plan -Zunstable-options"); + execs.masquerade_as_nightly_cargo(); + if is_natvis_supported() { + execs.with_json( + r#" + { + "inputs": [ + "[..]/foo/Cargo.toml" + ], + "invocations": [ + { + "args": [ + "--crate-name", + "natvis_disabled", + "--edition=2018", + "src/main.rs", + "--error-format=json", + "--json=[..]", + "--crate-type", + "bin", + "--emit=[..]", + "-C", + "embed-bitcode=[..]", + "-C", + "debuginfo=[..]", + "-C", + "metadata=[..]", + "--out-dir", + "[..]", + "-L", + "dependency=[..]" + ], + "cwd": "[..]/cit/[..]/foo", + "deps": [], + "env": "{...}", + "kind": null, + "links": "{...}", + "outputs": "{...}", + "package_name": "natvis_disabled", + "package_version": "0.0.1", + "program": "rustc", + "target_kind": ["bin"], + "compile_mode": "build" + } + ] + } + "#, + ); + } + execs.run(); +}