-
Notifications
You must be signed in to change notification settings - Fork 2.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add error-chain errors. #4090
Add error-chain errors. #4090
Conversation
Convert CargoResult, CargoError into an implementation provided by error-chain. The previous is_human machinery is mostly removed; now errors are displayed unless of the Internal kind, verbose mode will print all errors.
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @brson (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
@alexcrichton As discussed on the issue thread, here's the PR for review. Currently |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
src/cargo/ops/cargo_test.rs
Outdated
@@ -100,7 +101,7 @@ fn run_unit_tests(options: &TestOptions, | |||
shell.status("Running", cmd.to_string()) | |||
})?; | |||
|
|||
if let Err(e) = cmd.exec() { | |||
if let Err(CargoError(CargoErrorKind::ProcessErrorKind(e), .. )) = cmd.exec() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it may accidentally ignore all other errors, is that intended?
@@ -526,7 +527,7 @@ fn add_plugin_deps(rustc: &mut ProcessBuilder, | |||
let mut search_path = env::split_paths(&search_path).collect::<Vec<_>>(); | |||
for id in build_scripts.plugins.iter() { | |||
let key = (id.clone(), Kind::Host); | |||
let output = build_state.get(&key).chain_error(|| { | |||
let output = build_state.get(&key).ok_or_else(|| { | |||
internal(format!("couldn't find libs for plugin dep {}", id)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally we could get rid of internal
and human
as function (I think error-chain
just supports strings strings in these closures), do you think that'd be feasible? Or maybe happen in a future PR?
src/cargo/sources/directory.rs
Outdated
human(format!("failed to load checksum `.cargo-checksum.json` \ | ||
of {} v{}", | ||
pkg.package_id().name(), | ||
pkg.package_id().version())) | ||
|
||
})?; | ||
let cksum: Checksum = serde_json::from_str(&cksum).chain_error(|| { | ||
let cksum: Checksum = serde_json::from_str(&cksum) | ||
.map_err(CargoError::from).chain_err(|| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this map_err
necessary? Or is an extra foreign_links
just needed?
src/cargo/sources/git/utils.rs
Outdated
@@ -132,7 +133,7 @@ impl GitRemote { | |||
} | |||
fs::create_dir_all(dst)?; | |||
let repo = git2::Repository::init_bare(dst)?; | |||
fetch(&repo, &url, "refs/heads/*:refs/heads/*", cargo_config)?; | |||
fetch(&repo, &url, "refs/heads/*:refs/heads/*", cargo_config)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(trailing whitespace)
src/cargo/sources/git/utils.rs
Outdated
@@ -231,20 +232,22 @@ impl<'a> GitCheckout<'a> { | |||
fn clone_repo(source: &Path, into: &Path) -> CargoResult<git2::Repository> { | |||
let dirname = into.parent().unwrap(); | |||
|
|||
fs::create_dir_all(&dirname).chain_error(|| { | |||
fs::create_dir_all(&dirname).map_err(CargoError::from).chain_err(|| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to above, these map_err
instances shouldn't be necessary, right?
src/cargo/sources/git/utils.rs
Outdated
let object = self.repo.find_object(self.revision.0, None)?; | ||
self.repo.reset(&object, git2::ResetType::Hard, None)?; | ||
self.repo.find_object(self.revision.0, None) | ||
.and_then(|obj| {self.repo.reset(&obj, git2::ResetType::Hard, None)})?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this use ?
instead of and_then
?
self().chain_error(callback) | ||
error_chain! { | ||
types { | ||
CargoError, CargoErrorKind, CargoResultExt, CargoResult; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think eventually we'll want to not override the names here, but I like the idea of leaving this here for now to reduce the churn in the PR
src/cargo/util/errors.rs
Outdated
_ => false | ||
} | ||
}, | ||
_ => false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure we explicitly list it out, mind expanding out this _
to match everything?
src/cargo/util/network.rs
Outdated
// NetworkError chain | ||
error_chain!{ | ||
types { | ||
NetworkError, NetworkErrorKind, NetworkResultExt, NetworkResult; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was it possible to fold this into the main error_chain
chain?
Some quick responses, and I can try more stuff tonight:
I assumed that mapping the error type was necessary to access `chain_err`.
I'll have to double-check my expanded macro contents, to see if
error-chain's trait gets impl'd in such a way as to permit chaining on
arbitrary `Error + 'static`, or just experiment by pulling one out and
seeing if it still compiles.
cargo_test.rs: The original method returned only that error kind. For
future-safety, I should make sure the test appropriately handles all other
error kinds, for whatever appropriate means in context.
`internal`/`human`: Right now `human` is a wrapper around the `String`
error kind, so that's trivially removable. `internal` is the nice
constructor for the `Internal` error kind, which is the replacement for
`is_human` when determining when to stop printing non-verbose error
information. `is_human` is still there, but solely to support feeding
`CliError.unknown`, which decides whether to print ¯\_(ツ)_/¯ or the actual
error message. These things can be removed, but not without changing
cargo's behavior, so my preference is to work it after I make sure I didn't
break anything.
network.rs: This is the network error type half-removed (my first cut at it
had all the git and curl error types only in `NetworkError`). Since
`maybe_spurious` is only used in the retry logic itself, I should be able
to define that function where it's needed, then collapse the `NetworkError`
the rest of the way into `CargoError`.
|
☔ The latest upstream changes (presumably #4088) made this pull request unmergeable. Please resolve the merge conflicts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks awesome, @jluner !
internal(format!("failed to deserialize json")) | ||
})?; | ||
let old_fingerprint = serde_json::from_str(&old_fingerprint_json) | ||
.chain_err(|| {internal(format!("failed to deserialize json"))})?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{}
can be dropped.
src/cargo/lib.rs
Outdated
print(err.to_string(), shell); | ||
} | ||
} | ||
else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not that it really matters, but usually we format else as } else {
in Cargo.
src/cargo/util/toml.rs
Outdated
@@ -1003,7 +1004,8 @@ impl TomlManifest { | |||
|
|||
let dep = replacement.to_dependency(spec.name(), cx, None)?; | |||
let dep = { | |||
let version = spec.version().chain_error(|| { | |||
let version = spec.version().ok_or_else(|| | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The brace probably wants to be on the previous line :)
tests/doc.rs
Outdated
assert!(stderr.contains("☃"), "no snowman\n{}", stderr); | ||
assert!(stderr.contains("unknown start of token"), "no message\n{}", stderr); | ||
}else{ | ||
assert!(false, "an error kind other than ProcessErrorKind was encountered\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for \n
because we don't show stderr
afterwords.
src/cargo/lib.rs
Outdated
fn print(error: String, shell: &mut MultiShell) { | ||
let _ = shell.err().say("\nCaused by:", BLACK); | ||
let _ = shell.err().say(format!(" {}", error), BLACK); | ||
} | ||
|
||
unsafe fn extend_lifetime(r: &Error) -> &'static Error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could have a safer signature:
unsafe fn extend_lifetime(r: &Error) -> &(Error + 'static) {
std::mem::transmute::<&Error, &Error>(r)
}
It would be nice to add some short comment here, explaining why we need unsafe.
Convert CargoResult, CargoError into an implementation provided by error-chain. The previous is_human machinery is mostly removed; now errors are displayed unless of the Internal kind, verbose mode will print all errors.
Ok sounds great, we can pursue future refactorings after this PR. This is already a fantastic cleanup as it is :) |
* rebased * removed `human` (deferring removing `internal` to a later PR) * cargo_test.rs - fails on other error kinds * unnecessary `map_err(CargoError::from)` removed * fold NetworkError entirely into CargoError * added justification comment for `extend_lifetime` * various formatting goofs The following tests are currently failing: * `http_auth_offered` * `custom_build_script_failed` * `build_deps_for_the_right_arch` * `dep_with_bad_submodule` * `update_with_shared_deps` * `finds_author_email` * `finds_author_user` * `finds_author_user_escaped` * `finds_author_username` * `finds_git_author` * `exit_code`
The following tests are currently failing:
I might have done the rebase + push thing wrong? This PR is showing all the commits between my first checkin and tonight, and I don't think it's supposed to work that way. |
I just cloned rust-lang/cargo/master locally and ran tests, to make sure I don't bang my head against a wall fixing a test I didn't break, and |
@jluner oh that's odd, in theory all these tests should be isolated from the environment but we may have forgotten something to keep isolating them! |
Also feel free to just push to this branch and let CI take care of running tests, it may take a little longer but should get the job done! |
Dumb question time: How do I read the CI test results? I see that AppVeyor is the Windows builds and Travis is non-Windows. Travis shows lots of green entries; did it just skip trying other configurations after the first one built? Also, the travis failure seems to be formatting stuff, so I guess that has to be fixed before it will run tests? AppVeyor's errors appear to be failing to download something, rather than actually running the tests:
On the topic of formatting: Is there a specific cargo style guide I should reference? I initially tried running |
Ah yeah no worries, it's not always trivial to do so :) On PRs we test only one configuration on AppVeyor and Travis, everything else is basically skipped. An error like you're looking at looks like a spurious error so I'll just retry it. The automation basically runs |
Also nah we don't yet run rustfmt (I wouldn't recommend doing so) but I'd just in general follow the surrounding style. I can comment on any particular places as well |
So for example:
|
☔ The latest upstream changes (presumably #4107) made this pull request unmergeable. Please resolve the merge conflicts. |
Looks like CI is happy. What's the next step to getting error-chain to land before moving onto the next pass? |
Nice! I'll take a final pass at reviewing and then we can r+ to land this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Holy cow I'm blown away this didn't even change the test suite, very nice work! I'm super excited to land this!
I've left some mostly-just-minor comments, but if you've got any questions don't hesitate to ask. And it goes without saying, but thank you so much for taking this on @jluner!
@@ -309,7 +311,7 @@ fn execute_external_subcommand(config: &Config, cmd: &str, args: &[String]) -> C | |||
let command = match path { | |||
Some(command) => command, | |||
None => { | |||
return Err(human(match find_closest(config, cmd) { | |||
return Err(CargoError::from(match find_closest(config, cmd) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where possible I tend to find format!(...).into()
a little more ergonomic than wrapping with CargoError::from
, but that's not always possible due to inference issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In many cases the compiler was just not figuring it out. I can do another pass to make sure into
doesn't work. Is there a preference between a turbofished into
and from
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah unfortunately into
doesn't work w/ turbofish as the type parameter is in the wrong place, if .into()
doesn't work then CargoError::from
is fine, no worries!
src/bin/cargo.rs
Outdated
Err(CliError::code(code)) | ||
} else { | ||
Err(CliError::new(Box::new(err), 101)) | ||
if let CargoError(CargoErrorKind::ProcessErrorKind(ref perr), ..) = err { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a kind
method that should be able to extract this, perhaps that could be used here?
let mut path = url.path_segments().chain_error(|| { | ||
human(format!("pkgid urls must have a path: {}", url)) | ||
let mut path = url.path_segments().ok_or_else(|| { | ||
CargoError::from(format!("pkgid urls must have a path: {}", url)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think maybe .into()
would work here? (just as an example)
src/cargo/lib.rs
Outdated
@@ -30,6 +32,8 @@ extern crate url; | |||
|
|||
use std::io; | |||
use std::fmt; | |||
use std::error::Error; | |||
use error_chain::ChainedError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While you're at it mind throwing a newline above this import of error_chain
to separate the std and non-std imports?
let result = cmd.exec(); | ||
|
||
match result { | ||
Err(CargoError(CargoErrorKind::ProcessErrorKind(e), .. )) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the kind
method be used here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one's a little more awkward, because I have to check that the Result
is an Err
and then check the kind as well. Unpacking it might get verbose. I could if let Some(foo) = result.err()
and then if let blah = foo.kind()
. Or I suppose I could result.err().map(kind)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Upon review, we need to move the ProcessError
out here, but kind
gives a borrow.
src/cargo/sources/directory.rs
Outdated
pkg.package_id().name(), | ||
pkg.package_id().version())) | ||
let cksum: Checksum = serde_json::from_str(&cksum) | ||
.chain_err(|| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this call to chain_err
stay on the previous line? As-is the indentation looks a little odd
src/cargo/util/errors.rs
Outdated
fn description(&self) -> &str { self.error.description() } | ||
} | ||
impl CargoError { | ||
pub fn to_internal(self) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be called into_internal
to signify the transfer of ownership?
src/cargo/util/errors.rs
Outdated
} | ||
impl CargoError { | ||
pub fn to_internal(self) -> Self { | ||
//This is actually bad, because it loses the cause information for foreign_link |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to preserve self.0
somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore original comment, I was on the wrong track.
I'll look into preserving self.0 somehow.
src/cargo/util/network.rs
Outdated
|
||
fn maybe_spurious(err: &CargoError) -> bool { | ||
match err.kind() { | ||
&CargoErrorKind::Git(ref git_err) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indentation here looks a little off?
src/cargo/util/network.rs
Outdated
use git2; | ||
|
||
fn maybe_spurious(err: &CargoError) -> bool { | ||
match err.kind() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be modified to iterate over the error to see if anything along the way is spurious? In theory if there's any spurious error in the chain then this operation should be retried
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would need a trick like the one in lib.rs that walks the chain to print. There, I could convince myself that the transmute
-to-static trick is safe because the error was moved into the method and consumed entirely during printing. Here, we start with a &CargoError
. I don't have the confidence to say that transmuting the chain to static and downcasting here is similarly ok, just because I'm still too new to Rust. It seems like it might be ok? Maybe? I can at least put it in, and people wiser than I can review, and then just pull it back out if it's no good.
Fix some formatting items. Changes Internal error kind to preserve the original error. Changes network retry logic to inspect full error chain for spurious errors.
Alright this is looking great! Let's land this and continue to iterate in future PRs. @bors: r+ |
📌 Commit def249f has been approved by |
⌛ Testing commit def249f with merge 705c30b... |
💔 Test failed - status-travis |
Looks like it might be a cross-compilation issue? Or environment? Other jobs got past that successfully. |
I'll try to land #4113 first which should unblock this by just removing those configurations, they're all tested upstream in rust-lang/rust anyway. |
@bors: retry |
Add error-chain errors. Fixes #4209 Convert CargoResult, CargoError into an implementation provided by error-chain. The previous is_human machinery is mostly removed; now errors are displayed unless of the Internal kind, verbose mode will print all errors.
☀️ Test successful - status-appveyor, status-travis |
Woo! Thanks so much again for this @jluner! This is amazing 💖 |
Exciting! For a first contribution experience, this went well (modulo hardware problems locally). My next few questions are: I'm happy to keep contributing on this issue. Judging by my rate of productivity, I may have to end up handing some of it off or pausing work towards the middle or end of June. |
Oh typically with github and git you'll do development on branches vs the The two code-wise changes I could see are:
More broadly though I've long wanted for a deeper revamp with how Cargo treats errors. My basic thinking is that for every possible error you get out of Cargo we know how it'll be printed, so you've got an amount of context that you'll print out and an amount of context that you'll hide. A good example of this is network errors which should never hide anything, whereas if rustc fails we shouldn't print out the rustc command that failed, just that the crate failed (and rustc's output tells you why). On the other hand though we'll have bugs in Cargo where an error bubbles up that we didn't expect (e.g. an Finally I'm also thinking that all errors log all their context to the That's all pretty vague though and not super well thought out in my head. I'm sure others will have thoughts on this that will want to be voiced before any code actually gets written! Overall I've been not super satisfied with how Cargo's error handling story plays out, I fear that it hides too much by default which ends up being a pain :( |
Fixes #4209
Convert CargoResult, CargoError into an implementation provided by error-chain. The previous is_human machinery is mostly removed; now errors are displayed unless of the Internal kind, verbose mode will print all errors.