Skip to content

Commit dee3c02

Browse files
committed
NatVis support in Cargo
This allows developers to write NatVis descriptions for their crates. If a Cargo package contains NatVis files, then they will be written into the PDB file. Debuggers that support NatVis will automatically locate these files and use them. Currently, Visual Studio 2019, VS Code, and WinDbg support NatVis. (NatVis is a Windows-only feature.) The easiest way to add a NatVis file to a Cargo package is to create a `natvis` subdirectory and place the file in that directory. The file must have a `.natvis` extension. Any (reasonable) number of NatVis files can be provided. Optionally, you can give explicit paths by setting the `natvis-files` key in `Cargo.toml`. For example: ``` [package] name = ... natvis-files = ["my_types.natvis", "more_types.natvis"] ``` This key can also be set to `[]` in order to prevent Cargo from searching the `natvis` directory.
1 parent 134f614 commit dee3c02

File tree

7 files changed

+336
-1
lines changed

7 files changed

+336
-1
lines changed

src/cargo/core/compiler/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,15 @@ fn build_base_args(
943943
.env("RUSTC_BOOTSTRAP", "1");
944944
}
945945

946+
// If the target is using the MSVC toolchain, then pass any NatVis files to it.
947+
let target_config = bcx.target_data.target_config(unit.kind);
948+
let is_msvc = target_config.triple.ends_with("-msvc");
949+
if is_msvc {
950+
for natvis_file in unit.pkg.manifest().natvis_files().iter() {
951+
cmd.arg(format!("-Clink-arg=/natvis:{}", natvis_file));
952+
}
953+
}
954+
946955
// Add `CARGO_BIN_` environment variables for building tests.
947956
if unit.target.is_test() || unit.target.is_bench() {
948957
for bin_target in unit

src/cargo/core/manifest.rs

+7
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ pub struct Manifest {
5050
default_run: Option<String>,
5151
metabuild: Option<Vec<String>>,
5252
resolve_behavior: Option<ResolveBehavior>,
53+
natvis_files: Vec<String>,
5354
}
5455

5556
/// When parsing `Cargo.toml`, some warnings should silenced
@@ -384,6 +385,7 @@ impl Manifest {
384385
original: Rc<TomlManifest>,
385386
metabuild: Option<Vec<String>>,
386387
resolve_behavior: Option<ResolveBehavior>,
388+
natvis_files: Vec<String>,
387389
) -> Manifest {
388390
Manifest {
389391
summary,
@@ -407,6 +409,7 @@ impl Manifest {
407409
publish_lockfile,
408410
metabuild,
409411
resolve_behavior,
412+
natvis_files,
410413
}
411414
}
412415

@@ -539,6 +542,10 @@ impl Manifest {
539542
.join(".metabuild")
540543
.join(format!("metabuild-{}-{}.rs", self.name(), hash))
541544
}
545+
546+
pub fn natvis_files(&self) -> &[String] {
547+
&self.natvis_files
548+
}
542549
}
543550

544551
impl VirtualManifest {

src/cargo/core/package.rs

+7
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ pub struct SerializedPackage {
102102
links: Option<String>,
103103
#[serde(skip_serializing_if = "Option::is_none")]
104104
metabuild: Option<Vec<String>>,
105+
#[serde(skip_serializing_if = "Vec::is_empty")]
106+
natvis_files: Vec<String>,
105107
}
106108

107109
impl Package {
@@ -259,8 +261,13 @@ impl Package {
259261
links: self.manifest().links().map(|s| s.to_owned()),
260262
metabuild: self.manifest().metabuild().cloned(),
261263
publish: self.publish().as_ref().cloned(),
264+
natvis_files: self.natvis_files().to_vec(),
262265
}
263266
}
267+
268+
pub fn natvis_files(&self) -> &[String] {
269+
self.inner.manifest.natvis_files()
270+
}
264271
}
265272

266273
impl fmt::Display for Package {

src/cargo/util/config/target.rs

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ pub struct TargetConfig {
3434
/// running its build script and instead use the given output from the
3535
/// config file.
3636
pub links_overrides: BTreeMap<String, BuildOutput>,
37+
pub triple: String,
3738
}
3839

3940
/// Loads all of the `target.'cfg()'` tables.
@@ -85,6 +86,7 @@ pub(super) fn load_target_triple(config: &Config, triple: &str) -> CargoResult<T
8586
rustflags,
8687
linker,
8788
links_overrides,
89+
triple: triple.to_string(),
8890
})
8991
}
9092

src/cargo/util/toml/mod.rs

+67-1
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,8 @@ pub struct TomlProject {
836836
repository: Option<String>,
837837
metadata: Option<toml::Value>,
838838
resolver: Option<String>,
839+
#[serde(rename = "natvis-files")]
840+
natvis_files: Option<Vec<String>>,
839841
}
840842

841843
#[derive(Debug, Deserialize, Serialize)]
@@ -867,7 +869,7 @@ struct Context<'a, 'b> {
867869
}
868870

869871
impl TomlManifest {
870-
/// Prepares the manfiest for publishing.
872+
/// Prepares the manifest for publishing.
871873
// - Path and git components of dependency specifications are removed.
872874
// - License path is updated to point within the package.
873875
pub fn prepare_for_publish(
@@ -1297,6 +1299,15 @@ impl TomlManifest {
12971299
}
12981300
}
12991301

1302+
// Handle NatVis files. If the TomlManifest specifies a set of NatVis files, then we use
1303+
// exactly that set and we do not inspect the filesystem. (We do require that the paths
1304+
// specified in the manifest be relative paths.) If the manifest does not specify any
1305+
// NatVis files, then we look for `natvis/*.natvis` files.
1306+
let natvis_files = resolve_natvis_files(
1307+
project.natvis_files.as_ref().map(|s| s.as_slice()),
1308+
package_root,
1309+
)?;
1310+
13001311
let custom_metadata = project.metadata.clone();
13011312
let mut manifest = Manifest::new(
13021313
summary,
@@ -1319,6 +1330,7 @@ impl TomlManifest {
13191330
Rc::clone(me),
13201331
project.metabuild.clone().map(|sov| sov.0),
13211332
resolve_behavior,
1333+
natvis_files,
13221334
);
13231335
if project.license_file.is_some() && project.license.is_some() {
13241336
manifest.warnings_mut().add_warning(
@@ -1892,3 +1904,57 @@ impl fmt::Debug for PathValue {
18921904
self.0.fmt(f)
18931905
}
18941906
}
1907+
1908+
fn resolve_natvis_files(
1909+
toml_natvis_files: Option<&[String]>,
1910+
package_root: &Path,
1911+
) -> CargoResult<Vec<String>> {
1912+
if let Some(toml_natvis_files) = toml_natvis_files {
1913+
let mut natvis_files = Vec::with_capacity(toml_natvis_files.len());
1914+
for toml_natvis_file in toml_natvis_files.iter() {
1915+
let natvis_file_path = Path::new(toml_natvis_file);
1916+
if !natvis_file_path.is_relative() {
1917+
bail!(
1918+
"`natvis-files` contains absolute path `{}`; \
1919+
all paths in `natvis-files` are required to be relative paths.",
1920+
toml_natvis_file
1921+
);
1922+
}
1923+
natvis_files.push(
1924+
package_root
1925+
.join(natvis_file_path)
1926+
.to_str()
1927+
.unwrap()
1928+
.to_string(),
1929+
);
1930+
}
1931+
Ok(natvis_files)
1932+
} else {
1933+
// The manifest file did not specify any natvis files.
1934+
// By convention, we look for `natvis\*.natvis` files.
1935+
if let Ok(nv) = find_natvis_files(package_root) {
1936+
Ok(nv)
1937+
} else {
1938+
Ok(Vec::new())
1939+
}
1940+
}
1941+
}
1942+
1943+
fn find_natvis_files(package_root: &Path) -> std::io::Result<Vec<String>> {
1944+
use std::ffi::OsStr;
1945+
1946+
let mut natvis_files = Vec::new();
1947+
let natvis_dir = package_root.join("natvis");
1948+
for entry in std::fs::read_dir(&natvis_dir)? {
1949+
let entry = entry?;
1950+
let filename = PathBuf::from(entry.file_name().to_str().unwrap());
1951+
if filename.extension() == Some(OsStr::new("natvis")) {
1952+
if let Ok(md) = entry.metadata() {
1953+
if md.is_file() {
1954+
natvis_files.push(entry.path().to_str().unwrap().to_string());
1955+
}
1956+
}
1957+
}
1958+
}
1959+
Ok(natvis_files)
1960+
}

tests/testsuite/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ mod metabuild;
7575
mod metadata;
7676
mod minimal_versions;
7777
mod multitarget;
78+
mod natvis;
7879
mod net_config;
7980
mod new;
8081
mod offline;

0 commit comments

Comments
 (0)