Skip to content

Ability to suppress "Finished dev target" and "Running <path>" lines without hiding progress indicators #8743

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

Open
5225225 opened this issue Sep 29, 2020 · 12 comments
Labels
A-console-output Area: Terminal output, colors, progress bar, etc. C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-needs-team-input Status: Needs input from team on whether/how to proceed.

Comments

@5225225
Copy link
Contributor

5225225 commented Sep 29, 2020

Describe the problem you are trying to solve

I've started using cargo run --quiet (aliased to cr) to avoid the

Finished dev [unoptimized + debuginfo] target(s) in 0.06s
 Running `<path>`

lines that show up on every cargo run, since I often work with programs that only emit a few lines of output, and want to keep my scrollback as clean as possible, the lines are pure noise to me.

However, this also gets rid of progress indicators for downloads and builds, which make it hard to tell how how long it will be until the program starts, or even if the program has started yet, if cargo has things to do before running.

Describe the solution you'd like

A flag to just hide the Finished/Running lines that show up.

Alternatively, only show them if cargo is running in --verbose or had any actions to perform?

Or don't show them if they would have been the only output from cargo, show them otherwise? Not sure of the best method here.

@5225225 5225225 added the C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` label Sep 29, 2020
@ehuss ehuss added the A-console-output Area: Terminal output, colors, progress bar, etc. label Oct 20, 2020
@jschwe
Copy link
Contributor

jschwe commented Oct 6, 2022

I am interested in adding this (at least for the Finished line). Would there be capacity to review such a PR?

I would

  • add an unstable flag to hide the "Finished" line
  • Potentially this flag would only hide the "Finished" line if all Jobs were "Fresh" (in addition to the flag being present). The freshness check would probably make it more difficult to support suppressing the Running line, since the freshness information would need to be propogated.

@mcandre
Copy link

mcandre commented May 19, 2023

quiet should mean quiet

@bonsairobo
Copy link

I'm in favor of changing the default output to have a better UX, while still supporting the spammy output with --verbose.

Strawman proposal:

  1. No arguments: Only print warnings, errors, and a progress bar. I have to assume almost everyone is ignoring the spammy "Compiling foo" lines anyway.
  2. --verbose: Include the spam.
  3. --quiet: I think the current behavior is good. Some might argue that even warnings are not quiet enough?? I'm less aware of the motivations behind using --quiet.

@epage
Copy link
Contributor

epage commented Sep 25, 2024

To re-phrase my proposal in #8889, the idea I have is that output is split into "sticky" and "non-sticky".

  • Sticky output, like warnings an errors, persists
  • Non-sticky output (most status messages) will be cleared when their parent operation is done

I did say "most status messages", not "all". My ideal "first step" would be for each command to have a "summary" status message that is sticky. Due to how we compose things, this might initially be a "summary per step" (cargo test showing compile and test summaries) rather than a single "command summary" (test summary). Cargo scripts would have no run summary (maybe a compile summary if something was built). I think at that point we'd be in a better situation to evaluate removing the summary completely though I personally lean against it this time.

@samestep
Copy link

samestep commented Mar 6, 2025

I really wish this were a feature. The best alternative I've been able to come up with so far is this Python script that I got Claude to write today; it's a lot of really unnecessary complexity that I'd really rather avoid putting into my project:

import fcntl
import os
import pty
import re
import select
import shutil
import struct
import subprocess
import sys
import termios


def strip_ansi(text):
    """Strip ANSI escape sequences and hyperlinks from text."""
    # First remove OSC hyperlinks (everything between \x1b]8;; and \x1b\\)
    text = re.sub(r"\x1b]8;;.*?\x1b\\", "", text)
    # Then remove other ANSI sequences
    return re.sub(r"\x1b\[[^m]*m", "", text)


def is_status_message(line):
    """Check if a line is a status message that should be filtered."""
    return (
        line.startswith("Finished")
        or line.startswith("Running")
        or line.startswith("Blocking waiting for file lock")
        or not line  # empty lines
    )


def ensure_release_build(bin):
    # Create a pseudo-terminal to preserve color/progress output
    master, slave = pty.openpty()

    # Get the terminal size
    cols, rows = shutil.get_terminal_size()

    # Set the PTY window size
    # TIOCSWINSZ is terminal window size - see termios(3)
    TIOCSWINSZ = getattr(termios, "TIOCSWINSZ", 0x5414)
    s = struct.pack("HHHH", rows, cols, 0, 0)
    fcntl.ioctl(slave, TIOCSWINSZ, s)

    # Run cargo build --release with output going to our pty
    process = subprocess.Popen(
        ["cargo", "build", "--release", "--bin", bin],
        stderr=slave,
        # Don't pipe stdout - let it go straight to terminal
        env={
            **os.environ,
            "CARGO_TERM_PROGRESS_WHEN": "always",  # Always show progress
            "CARGO_TERM_COLOR": "always",  # Always show colors
            "CARGO_TERM_PROGRESS_WIDTH": str(cols),  # Set terminal width
            "TERM": os.environ.get("TERM", "xterm-256color"),  # Ensure TERM is set
        },
    )
    os.close(slave)  # Close slave end after process has it

    # Buffer for incomplete lines
    line_buffer = b""
    # Whether we've seen any non-status output
    has_real_output = False

    while True:
        # Wait for data to be available (100ms timeout)
        ready, _, _ = select.select([master], [], [], 0.1)
        if not ready:
            # No data available - check if process is done
            if process.poll() is not None:
                break
            continue

        # Read a chunk of data
        try:
            chunk = os.read(master, 4096)
        except OSError:
            # PTY was closed
            break
        if not chunk:
            break

        # Look for complete lines in the chunk
        while b"\n" in chunk:
            before, chunk = chunk.split(b"\n", 1)
            # Add any previous buffer content to this line
            line = line_buffer + before
            line_buffer = b""

            try:
                decoded = line.decode()
            except UnicodeDecodeError:
                # If we can't decode, just pass it through
                sys.stderr.buffer.write(line + b"\n")
                sys.stderr.buffer.flush()
                continue

            # Strip ANSI sequences and clean up the line for checking
            clean_line = strip_ansi(decoded).strip().replace("\r", "")
            if has_real_output or not is_status_message(clean_line):
                has_real_output = True
                sys.stderr.buffer.write(line + b"\n")
                sys.stderr.buffer.flush()

        # Store any remaining partial line
        line_buffer = chunk

        # If we've seen real output, also pass through any partial content immediately
        # This ensures progress indicators show up in real-time
        if has_real_output and line_buffer:
            sys.stderr.buffer.write(line_buffer)
            sys.stderr.buffer.flush()
            line_buffer = b""

    # Handle any remaining data
    if line_buffer and has_real_output:
        sys.stderr.buffer.write(line_buffer)
        sys.stderr.buffer.flush()

    # Clean up the pty
    os.close(master)

    # Check final status
    if process.wait() != 0:
        sys.exit(process.returncode)


def cargo_run(bin):
    # Ensure the release binary is built
    ensure_release_build()

    # Run the actual binary directly
    binary_path = f"target/release/{bin}"
    return subprocess.run([binary_path, *sys.argv[1:]]).returncode

@mcandre
Copy link

mcandre commented Mar 6, 2025

At least write the wrapper in Rust. This ain't the C/C++ ecosystem.

@epage
Copy link
Contributor

epage commented Mar 7, 2025

To add to my previous comment, this proposal adds a new, specialized knob for users to interact with. Each knob we add comes with a cost: code bloat, compatibility, and documentation bloat (the more that needs to be documented, the harder it is for people to discover any feature, the less value we get out of all features).

While my idea does not go to the extreme mentioned here, it goes most of the way and would more widely benefit people.

@samestep
Copy link

@epage it seems like it'd depend on the details of your proposal, but if it doesn't get rid of all build output when there's nothing to build, it doesn't solve my problem. Specifically, let me give a bit of context:

  • For my project, I have a Rust crate that serves as a centralized CLI, which replaced an earlier iteration that just comprised many separate Bash and Python helper scripts.
  • The most important subcommand of this CLI is the run subcommand, which takes as arguments two other commands, both of which also typically make use of the CLI.

For instance, here's the example from my project's README:

./gradbench run --eval "./gradbench repo eval hello" --tool "./gradbench repo tool pytorch"

Here, ./gradbench is a Python wrapper script that's essentially just a simplified version of the script I posted above; I didn't want to maintain all that complexity, so I sacrificed the Cargo progress bar instead.

As I mentioned above, those --eval and --tool arguments take other commands which will be run, concurrently. So, for instance, if the contents of ./gradbench were instead just a simple usage of cargo run, like this:

#!/usr/bin/env bash
cargo run --package=gradbench --profile=release-with-debug -- "$@"

The output every time I run the above command would look like this:

    Finished `release-with-debug` profile [optimized + debuginfo] target(s) in 0.10s
     Running `target/release-with-debug/gradbench run --eval './gradbench repo eval hello' --tool './gradbench repo tool pytorch'`
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on build directory
    Finished `release-with-debug` profile [optimized + debuginfo] target(s) in 0.06s
     Running `target/release-with-debug/gradbench repo eval hello`
    Finished `release-with-debug` profile [optimized + debuginfo] target(s) in 0.07s
     Running `target/release-with-debug/gradbench repo tool pytorch`
  [0] start hello (pytorch)
  [1] def   hello                                 700ms ✓
  [2] eval  hello::square   1.0                     0ms ~         0ms evaluate ✓
  [4] eval  hello::double   1.0                     0ms ~         0ms evaluate ✓
  [6] eval  hello::square   2.0                     0ms ~         0ms evaluate ✓
  [8] eval  hello::double   4.0                     0ms ~         0ms evaluate ✓
 [10] eval  hello::square   8.0                     0ms ~         0ms evaluate ✓
 [12] eval  hello::double   64.0                    0ms ~         0ms evaluate ✓
 [14] eval  hello::square   128.0                   0ms ~         0ms evaluate ✓
 [16] eval  hello::double   16384.0                 0ms ~         0ms evaluate ✓

The Cargo noise here is even longer than the actual output of my script, which is just unacceptable. Even if all the Blocking were removed, it'd still be 6 lines, which is still too long. Or even if only one line were printed per cargo run, that'd still be 3 lines here, which is still too long.

For a while I was using --quiet but had to switch to this Python script because it was unacceptable for people newly cloning my project from GitHub to have to wait a couple minutes before seeing any output.


Anyways, to summarize, if there's a different solution that doesn't add a "specialized knob" then I'm all for it; but without more details, I can't tell whether your proposal would actually help for this issue.

@epage
Copy link
Contributor

epage commented Mar 13, 2025

One of my goals for the reduced output is that there are not status messages for cargo-script (#12207), meaning there is no final status message for that style of invocation.

From there, the questions are

  • Do we support cargo Cargo.toml, giving it the same behavior as cargo foo.rs
    • Do we support resolving a symlink so you can ./foo which would resolve to ./Cargo.toml which would then be turned into cargo Cargo.toml when processing the shebang
  • Do we keep a status message for cargo run. Here there are several potential use cases so the answer is unclear. If we do, ideally it would be just one status message
    • Use case: cargo run is for development runs and we should give them context for what they are doing
    • Use case: cargo run is used for "scripting", like xtasks

@samestep
Copy link

Sure, I don't really have a preference among the following or other possible solutions, since any one of them would solve my problem, letting me replace my hacky Python script with a simple one- or two-line shell script:

  1. The default behavior of cargo build is changed to not print anything if there's nothing to do.
  2. A flag is added to cargo build to not print anything if there's nothing to do.
  3. The default behavior of cargo run is changed to not print any build output if there's nothing to build.
  4. A flag is added to cargo run to not print any build output if there's nothing to build.
  5. The cargo-script RFC is stabilized and can be used with a crate.

@samestep
Copy link

samestep commented Mar 13, 2025

Sorry, upon re-reading my last message I realize I was a bit unclear. What I meant to say was: @epage since I don't really care what form this solution takes, but you do, is there anything I can do to help at this point? Specifically, do you prefer one of these possible solutions (or perhaps a sixth option I forgot to list) over the others? If so, then is there a way I can help contribute an implementation? Or if not, is there a way I can help contribute to the discussion to help decide on a choice?

@epage
Copy link
Contributor

epage commented Mar 13, 2025

Cargo script is towards the end and will soon be stabilized. However, the output style for that is a short term hack.

I still feel the same that the first thing we should do is re-evaluate our baseline console output (see #8889). That will improve some of the problem and we can then evaluate how far we want to go within that that would cover your use case. If we don't, that gives us a better position for evaluate if and how we should cover your use case because we'd be working from a longer-term baseline rather than "how it works now".

For #8889, the two potential starting points are

  • Exploring how we know its safe to do richer, multi-line updating (including synchronizing the output). I've inherited a tradition of being leery of terminfo. Maybe we have a TERM allow list for defaulting to true, like we do for term.unicode, etc. Maybe its prevalent enough we just assume its supported (except on TERM=dumb) and require the user to opt-out via config.
  • Abstract our current progress handling so we don't have callers directly clearing lines This will give us a baseline for rolling our own or refactoring to using someone else's solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-console-output Area: Terminal output, colors, progress bar, etc. C-feature-request Category: proposal for a feature. Before PR, ping rust-lang/cargo if this is not `Feature accepted` S-needs-team-input Status: Needs input from team on whether/how to proceed.
Projects
None yet
Development

No branches or pull requests

8 participants