Skip to content

Commit ee7edd2

Browse files
committed
feat: cargo::error build script directive.
1 parent 9123040 commit ee7edd2

File tree

5 files changed

+115
-66
lines changed

5 files changed

+115
-66
lines changed

src/cargo/core/compiler/custom_build.rs

Lines changed: 64 additions & 26 deletions
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.
@@ -473,15 +489,18 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
473489
state.running(&cmd);
474490
let timestamp = paths::set_invocation_time(&script_run_dir)?;
475491
let prefix = format!("[{} {}] ", id.name(), id.version());
476-
let mut warnings_in_case_of_panic = Vec::new();
492+
let mut log_messages_in_case_of_panic = Vec::new();
477493
let output = cmd
478494
.exec_with_streaming(
479495
&mut |stdout| {
496+
if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
497+
log_messages_in_case_of_panic.push((Severity::Error, error.to_owned()));
498+
}
480499
if let Some(warning) = stdout
481500
.strip_prefix(OLD_CARGO_WARNING_SYNTAX)
482501
.or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
483502
{
484-
warnings_in_case_of_panic.push(warning.to_owned());
503+
log_messages_in_case_of_panic.push((Severity::Warning, warning.to_owned()));
485504
}
486505
if extra_verbose {
487506
state.stdout(format!("{}{}", prefix, stdout))?;
@@ -521,15 +540,29 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
521540
build_error_context
522541
});
523542

543+
// If the build failed
524544
if let Err(error) = output {
525-
insert_warnings_in_build_outputs(
545+
insert_log_messages_in_build_outputs(
526546
build_script_outputs,
527547
id,
528548
metadata_hash,
529-
warnings_in_case_of_panic,
549+
log_messages_in_case_of_panic,
530550
);
531551
return Err(error);
532552
}
553+
// ... or it logged any errors
554+
else if log_messages_in_case_of_panic
555+
.iter()
556+
.any(|(severity, _)| *severity == Severity::Error)
557+
{
558+
insert_log_messages_in_build_outputs(
559+
build_script_outputs,
560+
id,
561+
metadata_hash,
562+
log_messages_in_case_of_panic,
563+
);
564+
anyhow::bail!("build script logged errors");
565+
}
533566

534567
let output = output.unwrap();
535568

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

613-
/// When a build script run fails, store only warnings and nuke other outputs,
614-
/// as they are likely broken.
615-
fn insert_warnings_in_build_outputs(
646+
/// When a build script run fails, store only log messages, and nuke other
647+
/// outputs, as they are likely broken.
648+
fn insert_log_messages_in_build_outputs(
616649
build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
617650
id: PackageId,
618651
metadata_hash: Metadata,
619-
warnings: Vec<String>,
652+
log_messages: Vec<LogMessage>,
620653
) {
621-
let build_output_with_only_warnings = BuildOutput {
622-
warnings,
654+
let build_output_with_only_log_messages = BuildOutput {
655+
log_messages,
623656
..BuildOutput::default()
624657
};
625-
build_script_outputs
626-
.lock()
627-
.unwrap()
628-
.insert(id, metadata_hash, build_output_with_only_warnings);
658+
build_script_outputs.lock().unwrap().insert(
659+
id,
660+
metadata_hash,
661+
build_output_with_only_log_messages,
662+
);
629663
}
630664

631665
impl BuildOutput {
@@ -677,7 +711,7 @@ impl BuildOutput {
677711
let mut metadata = Vec::new();
678712
let mut rerun_if_changed = Vec::new();
679713
let mut rerun_if_env_changed = Vec::new();
680-
let mut warnings = Vec::new();
714+
let mut log_messages = Vec::new();
681715
let whence = format!("build script of `{}`", pkg_descr);
682716
// Old syntax:
683717
// cargo:rustc-flags=VALUE
@@ -849,15 +883,18 @@ impl BuildOutput {
849883
"rustc-link-search" => library_paths.push(PathBuf::from(value)),
850884
"rustc-link-arg-cdylib" | "rustc-cdylib-link-arg" => {
851885
if !targets.iter().any(|target| target.is_cdylib()) {
852-
warnings.push(format!(
853-
"{}{} was specified in the build script of {}, \
886+
log_messages.push((
887+
Severity::Warning,
888+
format!(
889+
"{}{} was specified in the build script of {}, \
854890
but that package does not contain a cdylib target\n\
855891
\n\
856892
Allowing this was an unintended change in the 1.50 \
857893
release, and may become an error in the future. \
858894
For more information, see \
859895
<https://github.com/rust-lang/cargo/issues/9562>.",
860-
syntax_prefix, key, pkg_descr
896+
syntax_prefix, key, pkg_descr
897+
),
861898
));
862899
}
863900
linker_args.push((LinkArgTarget::Cdylib, value))
@@ -943,10 +980,10 @@ impl BuildOutput {
943980
if nightly_features_allowed
944981
|| rustc_bootstrap_allows(library_name.as_deref())
945982
{
946-
warnings.push(format!("Cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
983+
log_messages.push((Severity::Warning, format!("Cannot set `RUSTC_BOOTSTRAP={}` from {}.\n\
947984
note: Crates cannot set `RUSTC_BOOTSTRAP` themselves, as doing so would subvert the stability guarantees of Rust for your project.",
948985
val, whence
949-
));
986+
)));
950987
} else {
951988
// Setting RUSTC_BOOTSTRAP would change the behavior of the crate.
952989
// Abort with an error.
@@ -962,7 +999,8 @@ impl BuildOutput {
962999
env.push((key, val));
9631000
}
9641001
}
965-
"warning" => warnings.push(value.to_string()),
1002+
"error" => log_messages.push((Severity::Error, value.to_string())),
1003+
"warning" => log_messages.push((Severity::Warning, value.to_string())),
9661004
"rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
9671005
"rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
9681006
"metadata" => {
@@ -987,7 +1025,7 @@ impl BuildOutput {
9871025
metadata,
9881026
rerun_if_changed,
9891027
rerun_if_env_changed,
990-
warnings,
1028+
log_messages,
9911029
})
9921030
}
9931031

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

Lines changed: 30 additions & 21 deletions
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

Lines changed: 6 additions & 0 deletions
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,6 +314,11 @@ 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+
316322
### `cargo::warning=MESSAGE` {#cargo-warning}
317323

318324
The `warning` instruction tells Cargo to display a warning after the build

tests/testsuite/build_script.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3903,21 +3903,22 @@ fn errors_and_warnings_emitted_and_build_failed() {
39033903
fn main() {
39043904
println!("cargo::warning=foo");
39053905
println!("cargo::warning=bar");
3906-
println!("cargo::warning=foo err");
3907-
println!("cargo::warning=bar err");
3906+
println!("cargo::error=foo err");
3907+
println!("cargo::error=bar err");
39083908
}
39093909
"#,
39103910
)
39113911
.build();
39123912

39133913
p.cargo("build")
3914+
.with_status(101)
39143915
.with_stderr_data(str![[r#"
39153916
[COMPILING] foo v0.5.0 ([ROOT]/foo)
39163917
[WARNING] [email protected]: foo
39173918
[WARNING] [email protected]: bar
3918-
[WARNING] [email protected]: foo err
3919-
[WARNING] [email protected]: bar err
3920-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
3919+
[ERROR] [email protected]: foo err
3920+
[ERROR] [email protected]: bar err
3921+
[ERROR] build script logged errors
39213922
39223923
"#]])
39233924
.run();
@@ -4008,11 +4009,8 @@ fn warnings_emitted_when_build_script_panics() {
40084009
.with_stdout_data("")
40094010
.with_stderr_data(str![[r#"
40104011
[COMPILING] foo v0.5.0 ([ROOT]/foo)
4011-
The following warnings were emitted during compilation:
4012-
40134012
[WARNING] [email protected]: foo
40144013
[WARNING] [email protected]: bar
4015-
40164014
[ERROR] failed to run custom build command for `foo v0.5.0 ([ROOT]/foo)`
40174015
40184016
Caused by:
@@ -4082,11 +4080,8 @@ fn warnings_emitted_when_dependency_panics() {
40824080
[DOWNLOADING] crates ...
40834081
[DOWNLOADED] published v0.1.0 (registry `dummy-registry`)
40844082
[COMPILING] published v0.1.0
4085-
The following warnings were emitted during compilation:
4086-
40874083
[WARNING] [email protected]: foo
40884084
[WARNING] [email protected]: bar
4089-
40904085
[ERROR] failed to run custom build command for `published v0.1.0`
40914086
40924087
Caused by:
@@ -4105,16 +4100,16 @@ Caused by:
41054100
}
41064101

41074102
#[cargo_test]
4108-
fn errors_and_warnings_emitted_when_dependency_logs_errors() {
4103+
fn log_messages_emitted_when_dependency_logs_errors() {
41094104
Package::new("published", "0.1.0")
41104105
.file(
41114106
"build.rs",
41124107
r#"
41134108
fn main() {
41144109
println!("cargo::warning=foo");
41154110
println!("cargo::warning=bar");
4116-
println!("cargo::warning=foo err");
4117-
println!("cargo::warning=bar err");
4111+
println!("cargo::error=foo err");
4112+
println!("cargo::error=bar err");
41184113
}
41194114
"#,
41204115
)
@@ -4150,14 +4145,18 @@ fn errors_and_warnings_emitted_when_dependency_logs_errors() {
41504145
.build();
41514146

41524147
p.cargo("build")
4148+
.with_status(101)
41534149
.with_stderr_data(str![[r#"
41544150
[UPDATING] `dummy-registry` index
41554151
[LOCKING] 2 packages to latest compatible versions
41564152
[DOWNLOADING] crates ...
41574153
[DOWNLOADED] published v0.1.0 (registry `dummy-registry`)
41584154
[COMPILING] published v0.1.0
4159-
[COMPILING] foo v0.5.0 ([ROOT]/foo)
4160-
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
4155+
[WARNING] [email protected]: foo
4156+
[WARNING] [email protected]: bar
4157+
[ERROR] [email protected]: foo err
4158+
[ERROR] [email protected]: bar err
4159+
[ERROR] build script logged errors
41614160
41624161
"#]])
41634162
.run();

0 commit comments

Comments
 (0)