Description
Problem
The current implementation of cargo::error
has a minimal, concise error presentation only when the build script exits with a successful status code (0) when it reports a build failure.
If a build script fails with a non-zero status code, use of the cargo::error
directive doesn't prevent Cargo from adding a full dump of stdout/stderr after it, which makes the output noisy and difficult to understand #10159.
The current implementation incentivises build scripts to exit with the status 0 (success) to cleanly report fatal build errors. This is a non-standard behavior for reporting failures, and it's not backwards compatible with older Cargo versions that don't understand cargo::error
.
Dumping the full stdout/stderr output is a major problem that diminishes usefulness of cargo::error
. The dump is so noisy that the errors reported via cargo::error
may be less visible and less readable than a raw text printed to stderr. The stdout dump can contain long lists of cargo::rerun-if-(env-)changed
directives, and other potentially irrelevant logs from subcommands. A long noisy stdout/stderr dump usually causes users to stop reading the output carefully, and gloss over the entire output instead. It may even push the "error:" lines out of the view, leaving only the raw dump on screen, which ends with less helpful content. The pkg-config
and cmake
crates have this problem, and they also happen to be the most frequent source of build script failures. pkg-config
prints 21 cargo::rerun-if-env-changed
lines, which together with Cargo's boilerplate is 25 lines, the same as the default height of cmd.exe
console on Windows. Windows users may literally not see the errors generated by cargo::error
if a build script fails with a failure status code. The long output dump can be made even noisier when the build script panics, and Cargo actively encourages users to enable even longer backtraces, which pushes the "error:" lines even further, and obscures even the last lines of stderr that may contain another copy of the error.
I think the conditions for when cargo::error=
has a clean presentation should be swapped: it should give the cleanest presentation when the build script reports cargo::error
and exits with a failure status code to match it. This is a consistent response (edit: or cargo::error
could have a clean output regardless of the status code).
(edit: Optional improvement suggestion, not the main problem): when a build script prints cargo::error
, but exits with a success code, this is a self-contradictory result. This is likely a bug in the build script. Such failures could be caused by helper functions or build-time dependencies eagerly printing cargo::error
, even when the build script manages to recover from the error and succeeds using some other way. To help catch such rogue cargo::error
s that break successful build scripts, Cargo should be printing the full stdout dump, so that build script authors can check where in the log the unintended directive has appeared.
In other words, this is the current behavior:
no structured errors | cargo::error= |
|
---|---|---|
exit code: success | success (clean output) | error (terse output) |
exit code: failure | dump all output | error + dump all output |
but to make cargo::error
useful to end users, and (edit: optionally) bad error reporting debuggable, it should work like this:
no structured errors | cargo::error= |
|
---|---|---|
exit code: success | success (clean output) | error + dump all output, or error (clean output) |
exit code: failure | dump all output | error (clean output) |
Steps
Buggy build.rs
:
use std::process::ExitCode;
fn main() -> ExitCode {
println!("cargo::error=oops");
ExitCode::SUCCESS
}
happens to get a clean error output with a high signal-to-noise ratio:
error: buildexample@0.1.0: oops
error: build script logged errors
A more typical script that exits with a correct status code:
fn main() {
println!("cargo::error=oops");
panic!("oops")
}
gets an ugly noisy dump that makes it hard for end users to understand what's going on:
error: buildexample@0.1.0: oops
error: failed to run custom build command for `buildexample v0.1.0 (/path/to/buildexample)`
note: To improve backtraces for build dependencies, set the CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable to enable debug information generation.
Caused by:
process didn't exit successfully: `/path/to/buildexample/target/debug/build/buildexample-0e711ca19eba66e9/build-script-build` (exit status: 101)
--- stdout
cargo::error=oops
--- stderr
thread 'main' panicked at build.rs:3:5:
oops
stack backtrace:
0: rust_begin_unwind
at /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/std/src/panicking.rs:665:5
1: core::panicking::panic_fmt
at /rustc/9fc6b43126469e3858e2fe86cafb4f0fd5068869/library/core/src/panicking.rs:76:14
2: build_script_build::main
3: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Possible Solution(s)
build scripts that report both failure and success at the same time (one via cargo::
the other via status) should get the stdout dump to help find the source of the contradictory response.
build scripts that print cargo::error=
and correctly return failure code should get a clean error output with just the errors and warnings the script reported, without irrelevant information dumped by Cargo.
Version
cargo 1.86.0-nightly (fd784878c 2025-01-03)