-
Notifications
You must be signed in to change notification settings - Fork 707
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
lib: rustfmt output to stdout #1042
Conversation
Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @fitzgen (or someone else) soon. |
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 is a good start!
We should add a smoke test to tests/tests.rs
asserting that these result in the same output:
cargo run <some simple test header>
rustfmt(cargo run --no-rustfmt-bindings <some simple test header>)
Additionally, we should remove write_to_stdout
and remove rustfmt_generated_file
. Instead, we should call rustfmt_generated_string
from write
before writing the source to the given writer
. This lets us share more code and avoid copies of things doing basically the same job.
Thanks @manaskarekar ! Let me know if you have any questions!
src/lib.rs
Outdated
cmd.args(&["--config-path", path]); | ||
} | ||
|
||
let mut child = cmd.spawn().expect("Failed to spawn rustfmt."); |
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 should propagate the error rather than unwrapping it with expect
.
src/lib.rs
Outdated
|
||
let child_stderr = stderr_handle.join() | ||
.expect("stderr reader thread should not have panicked") | ||
.expect("should have read child rustfmt's stderr OK"); |
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.
All these expect
s in this function should be propagating errors instead of unwrapping them.
src/lib.rs
Outdated
} | ||
|
||
/// Checks if rustfmt_bindings is set and runs rustfmt on the string | ||
fn rustfmt_generated_string(&self, source: String) -> io::Result<Option<(String, String)>> { |
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 we can just let the child's stderr
inherit our stderr
, and that will let us simplify this function and its signature a bit.
We should end up with:
fn rustfmt_generated_string(&self, source: String) -> io::Result<String> { ... }
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.
Hey @fitzgen, if we inherit stderr, how can we match on the status code of rustfmt to perform the respective actions? Just let the stderr display rustfmt's errors?
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.
We inherit stderr
by simply not calling .stderr(..)
on the command that is being built. It doesn't affect how the status code is returned; its orthogonal.
Does that make sense?
src/lib.rs
Outdated
let _t = self.context.timer("rustfmt_generated_string"); | ||
|
||
if !self.context.options().rustfmt_bindings { | ||
return Ok(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.
This should just return the source string unmodified, or else we won't print anything to stdout
at all.
src/lib.rs
Outdated
return Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
"Rustfmt activated, but it could not be found in global path.", | ||
)); |
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 should just return Ok(source)
as well. We don't want to avoid emitting bindings just because there is no rustfmt
(rustfmt
ing is configured on by default).
We can eprintln!("warning: could not find usable rustfmt to pretty print bindings")
to give users an idea why the bindings aren't pretty printed.
src/lib.rs
Outdated
)); | ||
}; | ||
|
||
let mut cmd = Command::new(rustfmt); |
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 is going to fail with rustfmt-nightly
which is where rustfmt
development is happening these days:
$ rustfmt --version
rustfmt: error while loading shared libraries: libsyntax-ff7df0d2d58ad322.so: cannot open shared object file: No such file or directory
$ rustup run nightly rustfmt -- --version
0.2.6-nightly ( )
We should do something like this:
let mut cmd = if let Ok(rustup) = which::which("rustup") {
let mut cmd = Command::new(rustup);
cmd.args(["run", "nightly", rustfmt, "--"]);
cmd
} else {
Command::new(rustfmt)
};
// ... continue configuring `cmd` ...
src/lib.rs
Outdated
|
||
// Ignore actual rustfmt status because it is often non-zero for trivial | ||
// things. | ||
let _ = child.wait().expect("should wait on rustfmt child OK"); |
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 was fine for tests, but now that this is for a wider audience, lets do what rustfmt_generated_file
is doing with the status code:
match output.status.code() {
Some(0) => Ok(()),
Some(2) => Err(io::Error::new(
io::ErrorKind::Other,
format!("Rustfmt parsing errors:\n{}", stderr),
)),
Some(3) => {
warn!(
"Rustfmt could not format some lines:\n{}",
stderr
);
Ok(())
}
_ => Err(io::Error::new(
io::ErrorKind::Other,
format!("Internal rustfmt error:\n{}", stderr),
)),
}
You can see more information about rustfmt
's exit codes with rustfmt --help
:
$ rustup run nightly rustfmt -- --help
<snip>
Exit Codes:
0 = No errors
1 = Encountered operational errors e.g. an IO error
2 = Failed to reformat code because of parsing errors
3 = Code is valid, but it is impossible to format it properly
4 = Formatted code differs from existing code (write-mode diff only)
Thank you for taking the time to give me thorough feedback @fitzgen. I will make the relevant changes As an aside, this continues to be a great exercise for a crash course through rust! Thank you. |
Looks like there's a bunch of compilation errors:
Let me know when you have all the tests passing and this is ready for me to look at again. Also, let's squash this PR into a single commit to hide the WIP commits from the git history which improves bisecting: https://github.com/servo/servo/wiki/Beginner's-guide-to-rebasing-and-squashing Thanks @manaskarekar ! |
Sorry about that, Nick, I'll make the changes accordingly. I had a few questions that I'll ask on chat. Thanks for your help in reviewing it! |
☔ The latest upstream changes (presumably #1079) made this pull request unmergeable. Please resolve the merge conflicts. |
ad4fe98
to
066ea49
Compare
// thread. This keeps the child from blocking on writing to its stdout which | ||
// might block us from writing to its stdin. | ||
let stdin_handle = ::std::thread::spawn(move || { | ||
let _ = child_stdin.write_all(source.as_bytes()); |
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.
@fitzgen What would be a better way to handle this "let _"?
Perhaps we can do something like:
match child_stdin.write_all(source.as_bytes()) {
Ok(_) => {},
Err(e) => {
eprintln!("Failed to write to rustfmt's stdin with error {}", e);
return source;
}
}
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.
That looks fine by me.
if let Ok(mut child) = cmd.spawn() { | ||
|
||
let mut child_stdin = child.stdin.take().unwrap(); | ||
let mut child_stdout = child.stdout.take().unwrap(); |
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.
@fitzgen Is there a better way than to unwrap 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.
No, unwrap is fine because we know it is Some
becuse we asked it to be Stdio::piped()
|
||
let status = child.wait()?; | ||
|
||
let source = stdin_handle.join().unwrap(); |
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.
@fitzgen How can (and should) we avoid the unwrap 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.
If we want to, we can
.map_err(|_| io::Error::new(io::ErrorKind::Other, "panicked writing to `rustfmt` stdin".into())
and then use the ?
operator.
But I don't see any way that thread should be able to panic, so unwrap
is probably fine.
if let Ok(mut child) = cmd.spawn() { | ||
|
||
let mut child_stdin = child.stdin.take().unwrap(); | ||
let mut child_stdout = child.stdout.take().unwrap(); |
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, unwrap is fine because we know it is Some
becuse we asked it to be Stdio::piped()
// thread. This keeps the child from blocking on writing to its stdout which | ||
// might block us from writing to its stdin. | ||
let stdin_handle = ::std::thread::spawn(move || { | ||
let _ = child_stdin.write_all(source.as_bytes()); |
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.
That looks fine by me.
|
||
let status = child.wait()?; | ||
|
||
let source = stdin_handle.join().unwrap(); |
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.
If we want to, we can
.map_err(|_| io::Error::new(io::ErrorKind::Other, "panicked writing to `rustfmt` stdin".into())
and then use the ?
operator.
But I don't see any way that thread should be able to panic, so unwrap
is probably fine.
match status.code() { | ||
Some(2) => Err(io::Error::new( | ||
io::ErrorKind::Other, | ||
format!("Rustfmt parsing errors."), |
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.
"...".into()
is better than using format!
I believe that the test failures on CI are due to not using Are the tests passing for you locally? |
EDIT: The tests pass locally only after the first run, due to the generated expectations/tests being updated in the first run. |
I changed the rustfmt to point the correct one and some tests seem to be failing due to formatting. I'm not sure if these changes are due to rustfmt updates. The generated expectations/tests are showing some weird formatting with extra spaces etc. I wonder if the rustfmt.toml needs a tweak. |
Are you sure you have the most up-to-date
Because after doing that locally, this doesn't produce any diffs for me:
If you can push any local changes you have, then I can try and pull this branch down and poke at it. |
Simplify the rustfmt and write mechanism. Use rustfmt generated string to allow writing to stdout or to rustfmt a file.
Change path to rustfmt.toml. Some tests fail due to formatting differences in expectations and generated bindings.
I think the issue was
All the tests are passing now, and bidnings are properly Moving to #1109 instead of this PR. Thanks for the persistence @manaskarekar ! |
lib: rustfmt output to stdout *(Recreated + slightly touched up version of #1042 + a version bump)* Simplify the rustfmt and write mechanism. Use rustfmt generated string to allow writing to stdout or to rustfmt a file.
I was just looking into this. Thanks! |
Taking a crack at issue #968.
I have yet to write tests, but would appreciate some comments on what I have so far.
Any help appreciated from r? @fitzgen or others.
Thank you.