Skip to content

Presentation of cargo::error encourages build scripts to exit with a successful status code on failure #15038

Open
@kornelski

Description

@kornelski

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::errors 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-build-scriptsArea: build.rs scriptsC-bugCategory: bugS-triageStatus: This issue is waiting on initial triage.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions