Skip to content
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

Use stderr for Cabal's diagnostic messages instead of stdout #4789

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

Rufflewind
Copy link
Member

@Rufflewind Rufflewind commented Sep 23, 2017

Please include the following checklist in your PR:

  • Patches conform to the coding conventions.
  • Any changes that could be relevant to users have been recorded in the changelog.
  • The documentation has been updated, if necessary.
  • If the change is docs-only, [ci skip] is used to avoid triggering the build bots.

Please also shortly describe how you tested your change. Bonus points for added tests!

Manually tested on a minimal Cabal project. cabal build 2>/dev/null shows nothing.

Fixes #4652.

@mention-bot
Copy link

@Rufflewind, thanks for your PR! By analyzing the history of the files in this pull request, we identified @dcoutts, @ezyang and @phadej to be potential reviewers.

Cabal/Cabal.cabal Outdated Show resolved Hide resolved
@hvr
Copy link
Member

hvr commented Sep 23, 2017

Does this only affect new-run or does this mean that effectively all output of say cabal new-build will suddenly go to stderr? Or put differently, which output will go to stdout at all after this patch? To be honest, I'm not convinced everything should go to stderr, as this doesn't match my intuition from other buildtools (most notably make) which use stdout for info/progress messages, while using stderr to denote critical errors and exceptions.

@Rufflewind
Copy link
Member Author

Does this only affect new-run or does this mean that effectively all output of say cabal new-build will suddenly go to stderr?

It should affect everything, since I rewired all the debug/notice/info functions per @23Skidoo's suggestion.

I personally only care about #4652 in relation to run, test, and bench, but this happened to be the most straightforward implementation.


Not really sure what could be done about the need for process-1.2.1.0, short of falling back to using stdout if process-1.2.1.0 is unavailable.

@mgsloan
Copy link
Collaborator

mgsloan commented Sep 24, 2017

Stack switched to sending most output to stderr pretty early on, some discussion here - commercialhaskell/stack#604 commercialhaskell/stack#446 . It's worked out well so far, the primary motivation is to distinguish output you might want to handle programmatically from output that's purely intended to be diagnostic, read by humans. I think is true to the intent of stdout / stderr, or at least what it means these days - the "err" in stderr is a bit of a misnomer. See https://www.jstorimer.com/blogs/workingwithcode/7766119-when-to-use-stderr-instead-of-stdout

@hvr
Copy link
Member

hvr commented Sep 24, 2017

@mgsloan coincidentally, since I wrote that comment I investigated the topic a bit, and I come to a similar conclusion. And for things like new-run this rather clear-cut; but the question for me is what you consider something to handle programmatically in e.g. cabal new-build --dry where the diagnostic output is the normal output (and I often call cabal new-build --dry for the sake of handling its output programmatically!) :-)

@23Skidoo
Copy link
Member

23Skidoo commented Sep 24, 2017

Having to special-case stuff like --dry-run is annoying and we likely won't get everything right the first time. Also the same diagnostic emitted by --dry-run is emitted when you run e.g. new-build -v3, so going that route will require us to switch between stdout/stderr dynamically depending on mode.

For such corner cases, my preference in is to decide either on stderr or stdout and stick with that.

@23Skidoo
Copy link
Member

@Rufflewind Have you also tried grepping for putStr / putStrLn? They may be used in a couple of places in the codebase.

@BardurArantsson
Copy link
Collaborator

BardurArantsson commented Sep 24, 2017

@mgsloan I think it's more that the distinction between stdout/stderr never made sense in the first place. It obviously only works in extremely simplistic scenarios and is pointless for anything beyond that. (Hence, exit codes and the like. Why have a secondary signaling mechanism if stdout/stderr was a thing you could rely on? It's absurd and pointless. Slitting the output into two streams only leads to race conditions, etc.)

@Rufflewind
Copy link
Member Author

@hvr I removed the dependency on process-1.2.1.0. Turns out createProcess has a guard to make sure that standard handles are never closed, so createProcess_ is (luckily) not needed here.

@23Skidoo: Went through each of them again; see updated commit. The following files were not changed because:

Cabal/Distribution/Simple/Register.hs: intended output
Cabal/Distribution/Make.hs: help output
Cabal/Distribution/Parsec/Parser.hs: for testing
Cabal/Distribution/Simple.hs: help output
Cabal/Distribution/PackageDescription/Quirks.hs: unused function
cabal-install/Distribution/Client/Sandbox.hs: intended output
cabal-install/Distribution/Client/Init.hs: only useful for interactive use
cabal-install/Distribution/Client/List.hs: intended output
cabal-install/Distribution/Client/Upload.hs: only useful for interactive use
cabal-install/Distribution/Client/Check.hs: intended output
cabal-install/main/Main.hs: intended output

Copy link
Member

@23Skidoo 23Skidoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks OK overall modulo some comments, my only concern is handling of corner cases like --dry-run that @hvr mentioned. We will probably have to identify them one by one and manually switch to use stdout (maybe conditionally).

@@ -131,7 +133,8 @@ genBounds verbosity packageDBs repoCtxt comp platform progdb mSandboxPkgInfo
verbosity packageDBs repoCtxt comp platform progdb
mSandboxPkgInfo globalFlags freezeFlags

putStrLn boundsNeededMsg
hPutStrLn stderr boundsNeededMsg
hFlush stderr
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, why do you need to flush stderr? Isn't one of the points of stderr is that it's unbuffered?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added it as a precaution because I saw stdout being used a few lines later. I've removed it.

I think I saw stderr being set to line-buffered in the code somewhere, but I don't know for sure. But if that is the case then the flush shouldn't be needed.

@@ -54,7 +55,7 @@ runLogProgress verbosity (LogProgress m) =
}
step_fn :: LogMsg -> NoCallStackIO a -> NoCallStackIO a
step_fn doc go = do
putStrLn (render doc)
hPutStrLn stderr (render doc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be replaced with notice as well, I think...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted this particular change. Changing runLogProgress.step_fn to use notice instead of hPutStrLn breaks too many tests as notice will wrap the messages with -----BEGIN CABAL OUTPUT----- markers, causing them to get caught in the test output.

@@ -281,7 +281,8 @@ ghcInvocation :: ConfiguredProgram -> Compiler -> Platform -> GhcOptions
-> ProgramInvocation
ghcInvocation prog comp platform opts =
(programInvocation prog (renderGhcOptions comp platform opts)) {
progInvokePathEnv = fromNubListR (ghcOptExtraPath opts)
progInvokePathEnv = fromNubListR (ghcOptExtraPath opts),
progInvokeOutAsErr = True
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only do this for GHC? Perhaps it should be the default?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure. Do these other programs write non-essential messages to stdout? I think the use of stdout for progress messages as GHC does is not normal, but I suppose Cabal could be proactive and force everything to stderr.

@23Skidoo
Copy link
Member

CI failure is due to -Werror and a redundant import.

@Rufflewind
Copy link
Member Author

Removed redundant imports.

@23Skidoo
Copy link
Member

23Skidoo commented Sep 27, 2017

Looks like this PR breaks a whole load of tests, which I guess is expected -- the test suite expects the output to be on stdout.

@tfausak
Copy link
Collaborator

tfausak commented Jul 10, 2018

I recently ran into an annoyance where new-exec printed the following output to STDOUT:

Resolving dependencies...

I was able to work around it, but it would be nice if Cabal sent output like that to STDERR instead.

Is this PR the best chance for that happening? Should I follow any other issues or PRs?

Send all diagnostic messages (including debug, info, notice) to stderr
instead of stdout.  Likewise, redirect GHC's stdout to stderr as GHC
sends progress output to stdout due to GHC Trac haskell#3636.
@Rufflewind
Copy link
Member Author

All tests pass now.

Copy link
Collaborator

@phadej phadej left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the problem is that v2-run may output something to stdout, then this is no solution? What if I need to capture stderr of program I v2-run?

The only solution is to make -vsilent truly silent (i.e. changing manual putStrLns to primitives in Verbosity), use verboseUnmarkOutput where appropriate to not break tests.

@Rufflewind
Copy link
Member Author

What if I need to capture stderr of program I v2-run?

In that case, the stderr may contain messages from Cabal as well. This is reasonable because there is no general expectation that the contents of stderr has any structure. stderr is normally meant for human consumption; this contrasts with stdout, which may contain structured data or other outputs that the user intend to capture or forward to another program. It is uncommon to capture stderr and also to expect the same degree of structure as captured stdout.

The only solution is to make -vsilent truly silent

That is a separate and orthogonal issue. This PR is only intended to address the issue in which Cabal writes unstructured diagnostic messages to stdout.

Making Cabal truly silent is not without downsides, such as the inability for Cabal to return useful diagnostics to the user when something goes wrong. Some CLI tools (e.g. curl --silent --show-error) can be configured to be silent on success, but display error messages in stderr on failure.

@fumieval
Copy link

fumieval commented Sep 7, 2020

I've been annoyed by "Up to date" or build messages from cabal. I agree that making it completely silent is a separate issue; I do want to see errors from cabal if something went wrong.

@fredefox fredefox mentioned this pull request Nov 27, 2021
3 tasks
@mergify mergify bot added the merge delay passed Applied (usually by Mergify) when PR approved and received no updates for 2 days label Sep 1, 2022
@ulysses4ever ulysses4ever removed the merge delay passed Applied (usually by Mergify) when PR approved and received no updates for 2 days label Sep 3, 2022
@Kleidukos
Copy link
Member

Marking this PR as draft 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

cabal run: "Preprocessing executable" message shouldn't be in stdout