Skip to content

feat: add Build::message_logger for structured access to cc's messages - #1967

Merged
NobodyXu merged 5 commits into
rust-lang:mainfrom
waltuov:feat/message-output-sink
Oct 1, 2026
Merged

NobodyXu merged 5 commits into
rust-lang:mainfrom
waltuov:feat/message-output-sink

Conversation

@waltuov

@waltuov waltuov commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1769.

Adds Build::message_logger, following the structure suggested in review. A logger implements BuildMessageLogger and gets each message with a BuildMessageKind, the text as a BuildMessage, and extra data as &dyn Any:

  • GeneralWarning: every warning cc prints itself. extra is ().
  • StderrForwarding: each line a command cc runs writes to stderr, such as the compiler or the archiver. extra is the Command.
  • CommandFailed { is_detection_cmd, exit_status }: a command cc runs exits unsuccessfully. The text is the error cc returns, and extra is the Command. cc recovers from some of these, such as a failed --print-search-dirs query, so the build can still succeed. is_detection_cmd is true for those: --print-search-dirs, xcrun --show-sdk-version and compiler family detection. Checks whose failure cc expects, such as flag support probes and the ar call with D that cc retries without it, are not reported.

Following the discussion in the issue:

  • By default the logger mirrors the warnings and stderr lines, which are still printed. With cargo_warnings(false) they only go to the logger.
  • All other cargo: lines (rustc-link-lib, rustc-link-search, rerun-if-env-changed) stay on stdout.
  • log returns nothing, so the logger can't fail the build unless it panics. It gets &self, and synchronisation is up to it.

Details:

  • The logger lives in CargoOutput, as HuzaifaAbdulRehman suggested in the issue, so it reaches every command cc runs. Clones of a Build share it.
  • BuildMessageKind, its CommandFailed variant and BuildMessage are #[non_exhaustive]. BuildMessage only has Str for now and implements Display.
  • Build stays Clone, Send, Sync, Unpin, UnwindSafe and RefUnwindSafe. A private wrapper keeps the last two, since dyn BuildMessageLogger has neither.
  • Stderr is piped when warnings or a logger are on, so cargo_warnings(false) with a logger still captures it.
  • StderrForwarder gets the CargoOutput and the Command, so the serial path, the parallel path and the run_output helpers all forward through one function.
  • wait_on_child, run_output, the parallel runner and family detection's -E check now build the "command did not execute successfully" error with one helper, which also passes it to the logger. The detection commands get a copy of CargoOutput that sets is_detection_cmd. run_silent_on_error keeps its own error, so the failures it expects stay silent.
  • With parallel, a failed compile's error is still printed as a warning, but the logger gets it once, as CommandFailed.
  • Family detection output that cc hides unless cargo_debug is on stays hidden from the logger too, and so do its failures, apart from the zig --version check.
  • The Build that the flag support probe creates now shares the logger and the cargo_warnings setting. So with cargo_warnings(false), the probe's own warnings no longer reach stdout, like the main build's. Before, they were printed even with warnings off. This probe also runs for cc's own checks, such as inherited rustflags.
  • Stderr lines passed to the logger don't keep a trailing \r from Windows compilers, and are converted to UTF-8 lossily.
  • Errors that have no exit status, such as a command that can't be started, are returned as before and not passed to the logger. cargo_debug output isn't passed to it either.

Without a logger, a build's stdout is the same as on main, apart from the flag probe change above. Checked by comparing stdout on main and on this branch for successful, failing and MSVC builds, with and without parallel.

The message_logger test runs builds in a subprocess and checks:

  • mirroring: the logger's messages equal the cargo:warning= lines on stdout, including both compiles' stderr with the cc and ar commands as extra, and the flag probe's warnings
  • redirecting: with cargo_warnings(false) stdout has no warnings, the logger has them, and the rustc-link-* lines are still on stdout
  • cc's cpp_set_stdlib warning on MSVC arrives as GeneralWarning with ()
  • a failed compile: its stderr comes first, then CommandFailed with exit code 1, is_detection_cmd false and the command, and with parallel the error isn't passed again as a warning
  • expand: stderr that cc collects instead of streaming reaches the logger, followed by CommandFailed with is_detection_cmd false
  • a failed --print-search-dirs query for wasm32 arrives as CommandFailed with is_detection_cmd true, and cc still finds an archiver
  • with cargo_debug, family detection's failed -E arrives as CommandFailed with is_detection_cmd true
  • for iOS, a failed xcrun --show-sdk-version arrives with is_detection_cmd true and cc goes on, and a failed xcrun --show-sdk-path arrives with false and fails
  • clones share the logger, and None removes it
  • Build's auto traits

Removing any single routing point (each kind, run_output's stderr, the \r strip, the stderr pipe, the probe's logger or warnings setting, removal with None, the unwind impls, the detection flag for --print-search-dirs, xcrun --show-sdk-version and family detection, or family detection's -E routing) makes the test fail. Not covered: a last stderr line without a newline, since the shim always ends its lines, and family detection that succeeds, which the shim can't do.

Tested on Windows with and without --features parallel: whole suite, clippy with -D warnings, fmt, rustdoc with -D warnings, and a library check on Rust 1.88. Not run here: Linux, macOS, the MSRV check and dev-tools/cc-test.

This touches is_flag_supported_inner like #1965, which is merged now. The branch merges with main without conflicts, and the suite passes on the merged result.

…iter

The writer gets the lines cc prints as `cargo:warning=`: the compiler's
stderr and cc's own warnings. They are still printed as warnings unless
`cargo_warnings(false)` is set; other `cargo:` lines always go to
stdout. Clones and the flag check's inner `Build` share the writer, and
a failed write is reported once as a warning instead of failing the
build.

@NobodyXu NobodyXu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you!

How about we make the capture more structured?

Comment thread src/lib.rs Outdated
@waltuov waltuov changed the title feat: add Build::message_output to also write cc's messages to a writer feat: add Build::message_logger for structured access to cc's messages Sep 30, 2026

@NobodyXu NobodyXu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you!

I think we can elaborate the doc here by copying the related PR

Comment thread src/logger.rs Outdated
…er docs

`CommandFailed` is now a struct variant with `is_detection_cmd` and
`exit_status`. `is_detection_cmd` is true for commands whose failure cc
recovers from: `--print-search-dirs`, `xcrun --show-sdk-version` and
compiler family detection. Family detection's `-E` failure now goes
through the shared helper too, so it reaches the logger with
`cargo_debug`, like its other commands.
@waltuov

waltuov commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

The Check Motor OS failure is nightly std not building for that target (no SplitPathsRef in sys::paths::motor). #1968 fails the same job, so it isn't from this change.

@NobodyXu NobodyXu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thank you, LGTM!

@NobodyXu
NobodyXu merged commit eca35e1 into rust-lang:main Oct 1, 2026
82 of 83 checks passed
@waltuov
waltuov deleted the feat/message-output-sink branch October 1, 2026 08:20
@lockfile-maintenance lockfile-maintenance Bot mentioned this pull request Oct 9, 2026
1 task
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Option to write output to an impl Write rather than stdout

2 participants