Skip to content

Commit 4f74477

Browse files
committed
Auto merge of #14743 - ehuss:build-script-errors, r=epage
Allow build scripts to report error messages through `cargo::error` Adds the `cargo::error` build script directive. It is similar to `cargo::warning`, but it emits an error message and it also fails the build. This is a repost of #14435 with the tests updated, a note added to the documentation about using this in a library, and updating the MSRV. Closes #10159
2 parents 92f94a5 + 8639d7b commit 4f74477

File tree

5 files changed

+380
-69
lines changed

5 files changed

+380
-69
lines changed

src/cargo/core/compiler/custom_build.rs

+64-26
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ use std::path::{Path, PathBuf};
5050
use std::str::{self, FromStr};
5151
use std::sync::{Arc, Mutex};
5252

53+
/// A build script instruction that tells Cargo to display an error after the
54+
/// build script has finished running. Read [the doc] for more.
55+
///
56+
/// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-error
57+
const CARGO_ERROR_SYNTAX: &str = "cargo::error=";
5358
/// Deprecated: A build script instruction that tells Cargo to display a warning after the
5459
/// build script has finished running. Read [the doc] for more.
5560
///
@@ -60,6 +65,15 @@ const OLD_CARGO_WARNING_SYNTAX: &str = "cargo:warning=";
6065
///
6166
/// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-warning
6267
const NEW_CARGO_WARNING_SYNTAX: &str = "cargo::warning=";
68+
69+
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
70+
pub enum Severity {
71+
Error,
72+
Warning,
73+
}
74+
75+
pub type LogMessage = (Severity, String);
76+
6377
/// Contains the parsed output of a custom build script.
6478
#[derive(Clone, Debug, Hash, Default, PartialEq, Eq, PartialOrd, Ord)]
6579
pub struct BuildOutput {
@@ -82,11 +96,13 @@ pub struct BuildOutput {
8296
pub rerun_if_changed: Vec<PathBuf>,
8397
/// Environment variables which, when changed, will cause a rebuild.
8498
pub rerun_if_env_changed: Vec<String>,
85-
/// Warnings generated by this build.
99+
/// Errors and warnings generated by this build.
86100
///
87-
/// These are only displayed if this is a "local" package, `-vv` is used,
88-
/// or there is a build error for any target in this package.
89-
pub warnings: Vec<String>,
101+
/// These are only displayed if this is a "local" package, `-vv` is used, or
102+
/// there is a build error for any target in this package. Note that any log
103+
/// message of severity `Error` will by itself cause a build error, and will
104+
/// cause all log messages to be displayed.
105+
pub log_messages: Vec<LogMessage>,
90106
}
91107

92108
/// Map of packages to build script output.
@@ -474,15 +490,18 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
474490
state.running(&cmd);
475491
let timestamp = paths::set_invocation_time(&script_run_dir)?;
476492
let prefix = format!("[{} {}] ", id.name(), id.version());
477-
let mut warnings_in_case_of_panic = Vec::new();
493+
let mut log_messages_in_case_of_panic = Vec::new();
478494
let output = cmd
479495
.exec_with_streaming(
480496
&mut |stdout| {
497+
if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
498+
log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
499+
}
481500
if let Some(warning) = stdout
482501
.strip_prefix(OLD_CARGO_WARNING_SYNTAX)
483502
.or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
484503
{
485-
warnings_in_case_of_panic.push(warning.to_owned());
504+
log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
486505
}
487506
if extra_verbose {
488507
state.stdout(format!("{}{}", prefix, stdout))?;
@@ -522,15 +541,29 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
522541
build_error_context
523542
});
524543

544+
// If the build failed
525545
if let Err(error) = output {
526-
insert_warnings_in_build_outputs(
546+
insert_log_messages_in_build_outputs(
527547
build_script_outputs,
528548
id,
529549
metadata_hash,
530-
warnings_in_case_of_panic,
550+
log_messages_in_case_of_panic,
531551
);
532552
return Err(error);
533553
}
554+
// ... or it logged any errors
555+
else if log_messages_in_case_of_panic
556+
.iter()
557+
.any(|(severity, _)| *severity == Severity::Error)
558+
{
559+
insert_log_messages_in_build_outputs(
560+
build_script_outputs,
561+
id,
562+
metadata_hash,
563+
log_messages_in_case_of_panic,
564+
);
565+
anyhow::bail!("build script logged errors");
566+
}
534567

535568
let output = output.unwrap();
536569

@@ -611,22 +644,23 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
611644
Ok(job)
612645
}
613646

614-
/// When a build script run fails, store only warnings and nuke other outputs,
615-
/// as they are likely broken.
616-
fn insert_warnings_in_build_outputs(
647+
/// When a build script run fails, store only log messages, and nuke other
648+
/// outputs, as they are likely broken.
649+
fn insert_log_messages_in_build_outputs(
617650
build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
618651
id: PackageId,
619652
metadata_hash: Metadata,
620-
warnings: Vec<String>,
653+
log_messages: Vec<LogMessage>,
621654
) {
622-
let build_output_with_only_warnings = BuildOutput {
623-
warnings,
655+
let build_output_with_only_log_messages = BuildOutput {
656+
log_messages,
624657
..BuildOutput::default()
625658
};
626-
build_script_outputs
627-
.lock()
628-
.unwrap()
629-
.insert(id, metadata_hash, build_output_with_only_warnings);
659+
build_script_outputs.lock().unwrap().insert(
660+
id,
661+
metadata_hash,
662+
build_output_with_only_log_messages,
663+
);
630664
}
631665

632666
impl BuildOutput {
@@ -678,7 +712,7 @@ impl BuildOutput {
678712
let mut metadata = Vec::new();
679713
let mut rerun_if_changed = Vec::new();
680714
let mut rerun_if_env_changed = Vec::new();
681-
let mut warnings = Vec::new();
715+
let mut log_messages = Vec::new();
682716
let whence = format!("build script of `{}`", pkg_descr);
683717
// Old syntax:
684718
// cargo:rustc-flags=VALUE
@@ -850,15 +884,18 @@ impl BuildOutput {
850884
"rustc-link-search" => library_paths.push(PathBuf::from(value)),
851885
"rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
852886
if !targets.iter().any(|target| target.is_cdylib()) {
853-
warnings.push(format!(
854-
"{}{} was specified in the build script of {}, \
887+
log_messages.push((
888+
Severity::Warning,
889+
format!(
890+
"{}{} was specified in the build script of {}, \
855891
but that package does not contain a cdylib target\n\
856892
\n\
857893
Allowing this was an unintended change in the 1.50 \
858894
release, and may become an error in the future. \
859895
For more information, see \
860896
<https://github.com/rust-lang/cargo/issues/9562>.",
861-
syntax_prefix, key, pkg_descr
897+
syntax_prefix, key, pkg_descr
898+
),
862899
));
863900
}
864901
linker_args.push((LinkArgTarget::Cdylib, value))
@@ -944,10 +981,10 @@ impl BuildOutput {
944981
if nightly_features_allowed
945982
|| rustc_bootstrap_allows(library_name.as_deref())
946983
{
947-
warnings.push(format!("Cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
984+
log_messages.push((Severity::Warning, format!("Cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
948985
note: Crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
949986
val, whence
950-
));
987+
)));
951988
} else {
952989
// Setting RUSTC_BOOTSTRAP would change the behavior of the crate.
953990
// Abort with an error.
@@ -963,7 +1000,8 @@ impl BuildOutput {
9631000
env.push((key, val));
9641001
}
9651002
}
966-
"warning" => warnings.push(value.to_string()),
1003+
"error" => log_messages.push((Severity::Error, value.to_string())),
1004+
"warning" => log_messages.push((Severity::Warning, value.to_string())),
9671005
"rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
9681006
"rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
9691007
"metadata" => {
@@ -988,7 +1026,7 @@ impl BuildOutput {
9881026
metadata,
9891027
rerun_if_changed,
9901028
rerun_if_env_changed,
991-
warnings,
1029+
log_messages,
9921030
})
9931031
}
9941032

src/cargo/core/compiler/job_queue/mod.rs

+30-21
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pub use self::job::Freshness::{self, Dirty, Fresh};
132132
pub use self::job::{Job, Work};
133133
pub use self::job_state::JobState;
134134
use super::build_runner::OutputFile;
135+
use super::custom_build::Severity;
135136
use super::timings::Timings;
136137
use super::{BuildContext, BuildPlan, BuildRunner, CompileMode, Unit};
137138
use crate::core::compiler::descriptive_pkg_name;
@@ -684,8 +685,8 @@ impl<'gctx> DrainState<'gctx> {
684685
self.queue.finish(&unit, &artifact);
685686
}
686687
Err(error) => {
687-
let msg = "The following warnings were emitted during compilation:";
688-
self.emit_warnings(Some(msg), &unit, build_runner)?;
688+
let show_warnings = true;
689+
self.emit_log_messages(&unit, build_runner, show_warnings)?;
689690
self.back_compat_notice(build_runner, &unit)?;
690691
return Err(ErrorToHandle {
691692
error,
@@ -962,33 +963,37 @@ impl<'gctx> DrainState<'gctx> {
962963
}
963964
}
964965

965-
fn emit_warnings(
966+
fn emit_log_messages(
966967
&mut self,
967-
msg: Option<&str>,
968968
unit: &Unit,
969969
build_runner: &mut BuildRunner<'_, '_>,
970+
show_warnings: bool,
970971
) -> CargoResult<()> {
971972
let outputs = build_runner.build_script_outputs.lock().unwrap();
972973
let Some(metadata) = build_runner.find_build_script_metadata(unit) else {
973974
return Ok(());
974975
};
975976
let bcx = &mut build_runner.bcx;
976977
if let Some(output) = outputs.get(metadata) {
977-
if !output.warnings.is_empty() {
978-
if let Some(msg) = msg {
979-
writeln!(bcx.gctx.shell().err(), "{}\n", msg)?;
980-
}
981-
982-
for warning in output.warnings.iter() {
983-
let warning_with_package =
984-
format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), warning);
985-
986-
bcx.gctx.shell().warn(warning_with_package)?;
987-
}
988-
989-
if msg.is_some() {
990-
// Output an empty line.
991-
writeln!(bcx.gctx.shell().err())?;
978+
if !output.log_messages.is_empty()
979+
&& (show_warnings
980+
|| output
981+
.log_messages
982+
.iter()
983+
.any(|(severity, _)| *severity == Severity::Error))
984+
{
985+
let msg_with_package =
986+
|msg: &str| format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), msg);
987+
988+
for (severity, message) in output.log_messages.iter() {
989+
match severity {
990+
Severity::Error => {
991+
bcx.gctx.shell().error(msg_with_package(message))?;
992+
}
993+
Severity::Warning => {
994+
bcx.gctx.shell().warn(msg_with_package(message))?;
995+
}
996+
}
992997
}
993998
}
994999
}
@@ -1098,8 +1103,12 @@ impl<'gctx> DrainState<'gctx> {
10981103
artifact: Artifact,
10991104
build_runner: &mut BuildRunner<'_, '_>,
11001105
) -> CargoResult<()> {
1101-
if unit.mode.is_run_custom_build() && unit.show_warnings(build_runner.bcx.gctx) {
1102-
self.emit_warnings(None, unit, build_runner)?;
1106+
if unit.mode.is_run_custom_build() {
1107+
self.emit_log_messages(
1108+
unit,
1109+
build_runner,
1110+
unit.show_warnings(build_runner.bcx.gctx),
1111+
)?;
11031112
}
11041113
let unlocked = self.queue.finish(unit, &artifact);
11051114
match artifact {

src/doc/src/reference/build-scripts.md

+17-2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ one detailed below.
128128
* [`cargo::rustc-env=VAR=VALUE`](#rustc-env) --- Sets an environment variable.
129129
* [`cargo::rustc-cdylib-link-arg=FLAG`](#rustc-cdylib-link-arg) --- Passes custom
130130
flags to a linker for cdylib crates.
131+
- [`cargo::error=MESSAGE`](#cargo-error) --- Displays an error on the terminal.
131132
* [`cargo::warning=MESSAGE`](#cargo-warning) --- Displays a warning on the
132133
terminal.
133134
* [`cargo::metadata=KEY=VALUE`](#the-links-manifest-key) --- Metadata, used by `links`
@@ -313,13 +314,27 @@ link-arg=FLAG` option][link-arg] to the compiler, but only when building a
313314
`cdylib` library target. Its usage is highly platform specific. It is useful
314315
to set the shared library version or the runtime-path.
315316

317+
### `cargo::error=MESSAGE` {#cargo-error}
318+
319+
The `error` instruction tells Cargo to display an error after the build script
320+
has finished running, and then fail the build.
321+
322+
> Note: Build script libraries should carefully consider if they want to
323+
> use `cargo::error` versus returning a `Result`. It may be better to return
324+
> a `Result`, and allow the caller to decide if the error is fatal or not.
325+
> The caller can then decide whether or not to display the `Err` variant
326+
> using `cargo::error`.
327+
328+
> **MSRV:** Respected as of 1.84
329+
316330
### `cargo::warning=MESSAGE` {#cargo-warning}
317331

318332
The `warning` instruction tells Cargo to display a warning after the build
319333
script has finished running. Warnings are only shown for `path` dependencies
320334
(that is, those you're working on locally), so for example warnings printed
321-
out in [crates.io] crates are not emitted by default. The `-vv` "very verbose"
322-
flag may be used to have Cargo display warnings for all crates.
335+
out in [crates.io] crates are not emitted by default, unless the build fails.
336+
The `-vv` "very verbose" flag may be used to have Cargo display warnings for
337+
all crates.
323338

324339
## Build Dependencies
325340

0 commit comments

Comments
 (0)