Skip to content

Commit 6d4be78

Browse files
committed
Auto merge of #6157 - alexheretic:member-manifest-error, r=alexcrichton
Expose manifest error chain in CargoErrors Adds new `ManifestError`s to the `CargoError` causal chain. These errors pass on their display, but provide more detail on which manifests are at fault when failing to build a `Workspace`. This is useful for lib users, in particular rls, allowing lookup of a particular manifest file that caused the error. See #6144. For example a workspace _foo_ where a member _bar_ has an invalid toml manifest would have the error chain: - failed to parse manifest at `/home/alex/project/foo/bar/Cargo.toml` _ManifestError: /home/alex/project/foo/Cargo.toml_ - failed to parse manifest at `/home/alex/project/foo/bar/Cargo.toml` _ManifestError: /home/alex/project/foo/bar/Cargo.toml_ - failed to parse manifest at `/home/alex/project/foo/bar/Cargo.toml` - could not parse input as TOML - expected a value, found an equals at line 8 This will allow rls to point to a particular workspace member's manifest file when that manifest fails to deserialize, has invalid paths, etc. This change should not affect binary use.
2 parents 430d8ca + 25b7ad2 commit 6d4be78

File tree

6 files changed

+178
-9
lines changed

6 files changed

+178
-9
lines changed

src/cargo/core/workspace.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use core::{Dependency, PackageIdSpec};
1313
use core::{EitherManifest, Package, SourceId, VirtualManifest};
1414
use ops;
1515
use sources::PathSource;
16-
use util::errors::{CargoResult, CargoResultExt};
16+
use util::errors::{CargoResult, CargoResultExt, ManifestError};
1717
use util::paths;
1818
use util::toml::read_manifest;
1919
use util::{Config, Filesystem};
@@ -508,7 +508,8 @@ impl<'cfg> Workspace<'cfg> {
508508
.collect::<Vec<_>>()
509509
};
510510
for candidate in candidates {
511-
self.find_path_deps(&candidate, root_manifest, true)?;
511+
self.find_path_deps(&candidate, root_manifest, true)
512+
.map_err(|err| ManifestError::new(err, manifest_path.clone()))?;
512513
}
513514
Ok(())
514515
}

src/cargo/ops/cargo_read_manifest.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fn read_nested_packages(
153153
"skipping malformed package found at `{}`",
154154
path.to_string_lossy()
155155
);
156-
errors.push(err);
156+
errors.push(err.into());
157157
return Ok(());
158158
}
159159
Ok(tuple) => tuple,

src/cargo/util/errors.rs

+63
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::fmt;
44
use std::process::{ExitStatus, Output};
55
use std::str;
6+
use std::path::PathBuf;
67

78
use core::{TargetKind, Workspace};
89
use failure::{Context, Error, Fail};
@@ -72,6 +73,68 @@ impl fmt::Display for Internal {
7273
}
7374
}
7475

76+
/// Error wrapper related to a particular manifest and providing it's path.
77+
///
78+
/// This error adds no displayable info of it's own.
79+
pub struct ManifestError {
80+
cause: Error,
81+
manifest: PathBuf,
82+
}
83+
84+
impl ManifestError {
85+
pub fn new<E: Into<Error>>(cause: E, manifest: PathBuf) -> Self {
86+
Self {
87+
cause: cause.into(),
88+
manifest,
89+
}
90+
}
91+
92+
pub fn manifest_path(&self) -> &PathBuf {
93+
&self.manifest
94+
}
95+
96+
/// Returns an iterator over the `ManifestError` chain of causes.
97+
///
98+
/// So if this error was not caused by another `ManifestError` this will be empty.
99+
pub fn manifest_causes(&self) -> ManifestCauses {
100+
ManifestCauses { current: self }
101+
}
102+
}
103+
104+
impl Fail for ManifestError {
105+
fn cause(&self) -> Option<&Fail> {
106+
self.cause.as_fail().cause()
107+
}
108+
}
109+
110+
impl fmt::Debug for ManifestError {
111+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
112+
self.cause.fmt(f)
113+
}
114+
}
115+
116+
impl fmt::Display for ManifestError {
117+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118+
self.cause.fmt(f)
119+
}
120+
}
121+
122+
/// An iterator over the `ManifestError` chain of causes.
123+
pub struct ManifestCauses<'a> {
124+
current: &'a ManifestError,
125+
}
126+
127+
impl<'a> Iterator for ManifestCauses<'a> {
128+
type Item = &'a ManifestError;
129+
130+
fn next(&mut self) -> Option<Self::Item> {
131+
self.current = self.current.cause.downcast_ref()?;
132+
Some(self.current)
133+
}
134+
}
135+
136+
impl<'a> ::std::iter::FusedIterator for ManifestCauses<'a> {}
137+
75138
// =============================================================================
76139
// Process errors
77140
#[derive(Debug, Fail)]

src/cargo/util/toml/mod.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use core::{Dependency, Manifest, PackageId, Summary, Target};
1919
use core::{Edition, EitherManifest, Feature, Features, VirtualManifest};
2020
use core::{GitReference, PackageIdSpec, SourceId, WorkspaceConfig, WorkspaceRootConfig};
2121
use sources::{CRATES_IO_INDEX, CRATES_IO_REGISTRY};
22-
use util::errors::{CargoError, CargoResult, CargoResultExt};
22+
use util::errors::{CargoError, CargoResult, CargoResultExt, ManifestError};
2323
use util::paths;
2424
use util::{self, Config, ToUrl};
2525

@@ -30,17 +30,17 @@ pub fn read_manifest(
3030
path: &Path,
3131
source_id: &SourceId,
3232
config: &Config,
33-
) -> CargoResult<(EitherManifest, Vec<PathBuf>)> {
33+
) -> Result<(EitherManifest, Vec<PathBuf>), ManifestError> {
3434
trace!(
3535
"read_manifest; path={}; source-id={}",
3636
path.display(),
3737
source_id
3838
);
39-
let contents = paths::read(path)?;
39+
let contents = paths::read(path).map_err(|err| ManifestError::new(err, path.into()))?;
4040

41-
let ret = do_read_manifest(&contents, path, source_id, config)
42-
.chain_err(|| format!("failed to parse manifest at `{}`", path.display()))?;
43-
Ok(ret)
41+
do_read_manifest(&contents, path, source_id, config)
42+
.chain_err(|| format!("failed to parse manifest at `{}`", path.display()))
43+
.map_err(|err| ManifestError::new(err, path.into()))
4444
}
4545

4646
fn do_read_manifest(

tests/testsuite/main.rs

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ mod jobserver;
6666
mod local_registry;
6767
mod lockfile_compat;
6868
mod login;
69+
mod member_errors;
6970
mod metabuild;
7071
mod metadata;
7172
mod net_config;

tests/testsuite/member_errors.rs

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use cargo::core::Workspace;
2+
use cargo::util::{config::Config, errors::ManifestError};
3+
4+
use support::project;
5+
6+
/// Tests inclusion of a `ManifestError` pointing to a member manifest
7+
/// when that manifest fails to deserialize.
8+
#[test]
9+
fn toml_deserialize_manifest_error() {
10+
let p = project()
11+
.file(
12+
"Cargo.toml",
13+
r#"
14+
[project]
15+
name = "foo"
16+
version = "0.1.0"
17+
authors = []
18+
19+
[dependencies]
20+
bar = { path = "bar" }
21+
22+
[workspace]
23+
"#,
24+
)
25+
.file("src/main.rs", "fn main() {}")
26+
.file(
27+
"bar/Cargo.toml",
28+
r#"
29+
[project]
30+
name = "bar"
31+
version = "0.1.0"
32+
authors = []
33+
34+
[dependencies]
35+
foobar == "0.55"
36+
"#,
37+
)
38+
.file("bar/src/main.rs", "fn main() {}")
39+
.build();
40+
41+
let root_manifest_path = p.root().join("Cargo.toml");
42+
let member_manifest_path = p.root().join("bar").join("Cargo.toml");
43+
44+
let error = Workspace::new(&root_manifest_path, &Config::default().unwrap()).unwrap_err();
45+
eprintln!("{:?}", error);
46+
47+
let manifest_err: &ManifestError = error.downcast_ref().expect("Not a ManifestError");
48+
assert_eq!(manifest_err.manifest_path(), &root_manifest_path);
49+
50+
let causes: Vec<_> = manifest_err.manifest_causes().collect();
51+
assert_eq!(causes.len(), 1, "{:?}", causes);
52+
assert_eq!(causes[0].manifest_path(), &member_manifest_path);
53+
}
54+
55+
/// Tests inclusion of a `ManifestError` pointing to a member manifest
56+
/// when that manifest has an invalid dependency path.
57+
#[test]
58+
fn member_manifest_path_io_error() {
59+
let p = project()
60+
.file(
61+
"Cargo.toml",
62+
r#"
63+
[project]
64+
name = "foo"
65+
version = "0.1.0"
66+
authors = []
67+
68+
[dependencies]
69+
bar = { path = "bar" }
70+
71+
[workspace]
72+
"#,
73+
)
74+
.file("src/main.rs", "fn main() {}")
75+
.file(
76+
"bar/Cargo.toml",
77+
r#"
78+
[project]
79+
name = "bar"
80+
version = "0.1.0"
81+
authors = []
82+
83+
[dependencies]
84+
foobar = { path = "nosuch" }
85+
"#,
86+
)
87+
.file("bar/src/main.rs", "fn main() {}")
88+
.build();
89+
90+
let root_manifest_path = p.root().join("Cargo.toml");
91+
let member_manifest_path = p.root().join("bar").join("Cargo.toml");
92+
let missing_manifest_path = p.root().join("bar").join("nosuch").join("Cargo.toml");
93+
94+
let error = Workspace::new(&root_manifest_path, &Config::default().unwrap()).unwrap_err();
95+
eprintln!("{:?}", error);
96+
97+
let manifest_err: &ManifestError = error.downcast_ref().expect("Not a ManifestError");
98+
assert_eq!(manifest_err.manifest_path(), &root_manifest_path);
99+
100+
let causes: Vec<_> = manifest_err.manifest_causes().collect();
101+
assert_eq!(causes.len(), 2, "{:?}", causes);
102+
assert_eq!(causes[0].manifest_path(), &member_manifest_path);
103+
assert_eq!(causes[1].manifest_path(), &missing_manifest_path);
104+
}

0 commit comments

Comments
 (0)