Skip to content

Commit

Permalink
feat: cargo::error build script directive.
Browse files Browse the repository at this point in the history
  • Loading branch information
torhovland committed Aug 21, 2024
1 parent f2e8d6e commit 8c0028a
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 62 deletions.
37 changes: 27 additions & 10 deletions src/cargo/core/compiler/custom_build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ use std::path::{Path, PathBuf};
use std::str::{self, FromStr};
use std::sync::{Arc, Mutex};

/// A build script instruction that tells Cargo to display an error after the
/// build script has finished running. Read [the doc] for more.
///
/// [the doc]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#cargo-error
const CARGO_ERROR_SYNTAX: &str = "cargo::error=";
/// Deprecated: A build script instruction that tells Cargo to display a warning after the
/// build script has finished running. Read [the doc] for more.
///
Expand Down Expand Up @@ -82,10 +87,11 @@ pub struct BuildOutput {
pub rerun_if_changed: Vec<PathBuf>,
/// Environment variables which, when changed, will cause a rebuild.
pub rerun_if_env_changed: Vec<String>,
/// Warnings generated by this build.
/// Errors and warnings generated by this build.
///
/// These are only displayed if this is a "local" package, `-vv` is used,
/// or there is a build error for any target in this package.
pub errors: Vec<String>,
pub warnings: Vec<String>,
}

Expand Down Expand Up @@ -473,10 +479,14 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
state.running(&cmd);
let timestamp = paths::set_invocation_time(&script_run_dir)?;
let prefix = format!("[{} {}] ", id.name(), id.version());
let mut errors_in_case_of_panic = Vec::new();
let mut warnings_in_case_of_panic = Vec::new();
let output = cmd
.exec_with_streaming(
&mut |stdout| {
if let Some(error) = stdout.strip_prefix(CARGO_ERROR_SYNTAX) {
errors_in_case_of_panic.push(error.to_owned());
}
if let Some(warning) = stdout
.strip_prefix(OLD_CARGO_WARNING_SYNTAX)
.or(stdout.strip_prefix(NEW_CARGO_WARNING_SYNTAX))
Expand Down Expand Up @@ -522,10 +532,11 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
});

if let Err(error) = output {
insert_warnings_in_build_outputs(
insert_errors_and_warnings_in_build_outputs(
build_script_outputs,
id,
metadata_hash,
errors_in_case_of_panic,
warnings_in_case_of_panic,
);
return Err(error);
Expand Down Expand Up @@ -610,22 +621,25 @@ fn build_work(build_runner: &mut BuildRunner<'_, '_>, unit: &Unit) -> CargoResul
Ok(job)
}

/// When a build script run fails, store only warnings and nuke other outputs,
/// as they are likely broken.
fn insert_warnings_in_build_outputs(
/// When a build script run fails, store only errors and warnings, and nuke
/// other outputs, as they are likely broken.
fn insert_errors_and_warnings_in_build_outputs(
build_script_outputs: Arc<Mutex<BuildScriptOutputs>>,
id: PackageId,
metadata_hash: Metadata,
errors: Vec<String>,
warnings: Vec<String>,
) {
let build_output_with_only_warnings = BuildOutput {
let build_output_with_only_errors_and_warnings = BuildOutput {
errors,
warnings,
..BuildOutput::default()
};
build_script_outputs
.lock()
.unwrap()
.insert(id, metadata_hash, build_output_with_only_warnings);
build_script_outputs.lock().unwrap().insert(
id,
metadata_hash,
build_output_with_only_errors_and_warnings,
);
}

impl BuildOutput {
Expand Down Expand Up @@ -677,6 +691,7 @@ impl BuildOutput {
let mut metadata = Vec::new();
let mut rerun_if_changed = Vec::new();
let mut rerun_if_env_changed = Vec::new();
let mut errors = Vec::new();
let mut warnings = Vec::new();
let whence = format!("build script of `{}`", pkg_descr);
// Old syntax:
Expand Down Expand Up @@ -962,6 +977,7 @@ impl BuildOutput {
env.push((key, val));
}
}
"error" => errors.push(value.to_string()),
"warning" => warnings.push(value.to_string()),
"rerun-if-changed" => rerun_if_changed.push(PathBuf::from(value)),
"rerun-if-env-changed" => rerun_if_env_changed.push(value.to_string()),
Expand All @@ -987,6 +1003,7 @@ impl BuildOutput {
metadata,
rerun_if_changed,
rerun_if_env_changed,
errors,
warnings,
})
}
Expand Down
23 changes: 14 additions & 9 deletions src/cargo/core/compiler/job_queue/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,8 +684,9 @@ impl<'gctx> DrainState<'gctx> {
self.queue.finish(&unit, &artifact);
}
Err(error) => {
let msg = "The following warnings were emitted during compilation:";
self.emit_warnings(Some(msg), &unit, build_runner)?;
let msg =
"The following errors and warnings were emitted during compilation:";
self.emit_errors_and_warnings(Some(msg), &unit, build_runner)?;
self.back_compat_notice(build_runner, &unit)?;
return Err(ErrorToHandle {
error,
Expand Down Expand Up @@ -962,7 +963,7 @@ impl<'gctx> DrainState<'gctx> {
}
}

fn emit_warnings(
fn emit_errors_and_warnings(
&mut self,
msg: Option<&str>,
unit: &Unit,
Expand All @@ -974,16 +975,20 @@ impl<'gctx> DrainState<'gctx> {
};
let bcx = &mut build_runner.bcx;
if let Some(output) = outputs.get(metadata) {
if !output.warnings.is_empty() {
if !output.warnings.is_empty() || !output.errors.is_empty() {
if let Some(msg) = msg {
writeln!(bcx.gctx.shell().err(), "{}\n", msg)?;
}

for warning in output.warnings.iter() {
let warning_with_package =
format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), warning);
let msg_with_package =
|msg: &str| format!("{}@{}: {}", unit.pkg.name(), unit.pkg.version(), msg);

for error in output.errors.iter() {
bcx.gctx.shell().error(msg_with_package(error))?;
}

bcx.gctx.shell().warn(warning_with_package)?;
for warning in output.warnings.iter() {
bcx.gctx.shell().warn(msg_with_package(warning))?;
}

if msg.is_some() {
Expand Down Expand Up @@ -1099,7 +1104,7 @@ impl<'gctx> DrainState<'gctx> {
build_runner: &mut BuildRunner<'_, '_>,
) -> CargoResult<()> {
if unit.mode.is_run_custom_build() && unit.show_warnings(build_runner.bcx.gctx) {
self.emit_warnings(None, unit, build_runner)?;
self.emit_errors_and_warnings(None, unit, build_runner)?;
}
let unlocked = self.queue.finish(unit, &artifact);
match artifact {
Expand Down
16 changes: 11 additions & 5 deletions src/doc/src/reference/build-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ one detailed below.
* [`cargo::rustc-env=VAR=VALUE`](#rustc-env) --- Sets an environment variable.
* [`cargo::rustc-cdylib-link-arg=FLAG`](#rustc-cdylib-link-arg) --- Passes custom
flags to a linker for cdylib crates.
- [`cargo::error=MESSAGE`](#cargo-error) --- Displays an error on the terminal.
* [`cargo::warning=MESSAGE`](#cargo-warning) --- Displays a warning on the
terminal.
* [`cargo::metadata=KEY=VALUE`](#the-links-manifest-key) --- Metadata, used by `links`
Expand Down Expand Up @@ -313,13 +314,18 @@ link-arg=FLAG` option][link-arg] to the compiler, but only when building a
`cdylib` library target. Its usage is highly platform specific. It is useful
to set the shared library version or the runtime-path.

### `cargo::warning=MESSAGE` {#cargo-warning}
### `cargo::error=MESSAGE` {#cargo-error}

The `warning` instruction tells Cargo to display a warning after the build
script has finished running. Warnings are only shown for `path` dependencies
(that is, those you're working on locally), so for example warnings printed
The `error` instruction tells Cargo to display an error after the build
script has finished running. Errors are only shown for `path` dependencies
(that is, those you're working on locally), so for example errors printed
out in [crates.io] crates are not emitted by default. The `-vv` "very verbose"
flag may be used to have Cargo display warnings for all crates.
flag may be used to have Cargo display errors for all crates.

### `cargo::warning=MESSAGE` {#cargo-warning}

The `warning` instruction is just like [`error`](#cargo-error), except that it
will output warnings.

## Build Dependencies

Expand Down
32 changes: 20 additions & 12 deletions tests/testsuite/build_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3874,14 +3874,16 @@ fn errors_and_warnings_emitted() {
.build();

p.cargo("build -v")
.with_status(101)
.with_stderr_data(str![[r#"
[COMPILING] foo v0.5.0 ([ROOT]/foo)
[RUNNING] `rustc --crate-name build_script_build [..]`
[RUNNING] `[ROOT]/foo/target/debug/build/foo-[HASH]/build-script-build`
[ERROR] invalid output in build script of `foo v0.5.0 ([ROOT]/foo)`: `cargo::error=foo err`
Unknown key: `error`.
See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script for more information about build script outputs.
[ERROR] foo@0.5.0: foo err
[ERROR] foo@0.5.0: bar err
[WARNING] foo@0.5.0: foo
[WARNING] foo@0.5.0: bar
[RUNNING] `rustc --crate-name foo [..]`
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
Expand Down Expand Up @@ -3921,6 +3923,8 @@ fn errors_and_warnings_emitted_when_build_script_panics() {
.with_stdout_data("")
.with_stderr_data(str![[r#"
...
[ERROR] foo@0.5.0: foo err
[ERROR] foo@0.5.0: bar err
[WARNING] foo@0.5.0: foo
[WARNING] foo@0.5.0: bar
...
Expand Down Expand Up @@ -3974,7 +3978,6 @@ fn errors_and_warnings_hidden_for_upstream() {
.build();

p.cargo("build -v")
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
Expand All @@ -3983,9 +3986,10 @@ fn errors_and_warnings_hidden_for_upstream() {
[COMPILING] bar v0.1.0
[RUNNING] `rustc --crate-name build_script_build [..]`
[RUNNING] `[ROOT]/foo/target/debug/build/bar-[HASH]/build-script-build`
[ERROR] invalid output in build script of `bar v0.1.0`: `cargo::error=foo err`
Unknown key: `error`.
See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script for more information about build script outputs.
[RUNNING] `rustc --crate-name bar [..]`
[COMPILING] foo v0.5.0 ([ROOT]/foo)
[RUNNING] `rustc --crate-name foo [..]`
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
Expand Down Expand Up @@ -4037,7 +4041,6 @@ fn errors_and_warnings_printed_on_vv() {
.build();

p.cargo("build -vv")
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
Expand All @@ -4046,9 +4049,14 @@ fn errors_and_warnings_printed_on_vv() {
[COMPILING] bar v0.1.0
[RUNNING] `[..] rustc --crate-name build_script_build [..]`
[RUNNING] `[..] [ROOT]/foo/target/debug/build/bar-[HASH]/build-script-build`
[ERROR] invalid output in build script of `bar v0.1.0`: `cargo::error=foo err`
Unknown key: `error`.
See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script for more information about build script outputs.
[ERROR] bar@0.1.0: foo err
[ERROR] bar@0.1.0: bar err
[WARNING] bar@0.1.0: foo
[WARNING] bar@0.1.0: bar
[RUNNING] `[..] rustc --crate-name bar [..]`
[COMPILING] foo v0.5.0 ([ROOT]/foo)
[RUNNING] `[..] rustc --crate-name foo [..]`
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
Expand Down
44 changes: 18 additions & 26 deletions tests/testsuite/warn_on_failure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,14 @@ fn no_error_or_warning_on_success() {
let upstream = make_upstream("");
upstream
.cargo("build")
.with_status(101)
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[COMPILING] bar v0.0.1
[ERROR] invalid output in build script of `bar v0.0.1`: `cargo::error=Hello! I'm an error. :)`
Unknown key: `error`.
See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script for more information about build script outputs.
[COMPILING] foo v0.0.1 ([ROOT]/foo)
[FINISHED] `dev` profile [unoptimized + debuginfo] target(s) in [ELAPSED]s
"#]])
.run();
Expand All @@ -90,17 +88,14 @@ fn no_error_or_warning_on_bin_failure() {
.cargo("build")
.with_status(101)
.with_stdout_does_not_contain("hidden stdout")
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[COMPILING] bar v0.0.1
[ERROR] invalid output in build script of `bar v0.0.1`: `cargo::error=Hello! I'm an error. :)`
Unknown key: `error`.
See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script for more information about build script outputs.
"#]])
.with_stderr_does_not_contain("hidden stderr")
.with_stderr_does_not_contain(&format!("[ERROR] {}", ERROR))
.with_stderr_does_not_contain(&format!("[WARNING] {}", WARNING1))
.with_stderr_does_not_contain(&format!("[WARNING] {}", WARNING2))
.with_stderr_contains("[UPDATING] `[..]` index")
.with_stderr_contains("[DOWNLOADED] bar v0.0.1 ([..])")
.with_stderr_contains("[COMPILING] bar v0.0.1")
.with_stderr_contains("[COMPILING] foo v0.0.1 ([..])")
.run();
}

Expand All @@ -113,16 +108,13 @@ fn errors_and_warnings_on_lib_failure() {
.cargo("build")
.with_status(101)
.with_stdout_does_not_contain("hidden stdout")
.with_stderr_data(str![[r#"
[UPDATING] `dummy-registry` index
[LOCKING] 2 packages to latest compatible versions
[DOWNLOADING] crates ...
[DOWNLOADED] bar v0.0.1 (registry `dummy-registry`)
[COMPILING] bar v0.0.1
[ERROR] invalid output in build script of `bar v0.0.1`: `cargo::error=Hello! I'm an error. :)`
Unknown key: `error`.
See https://doc.rust-lang.org/cargo/reference/build-scripts.html#outputs-of-the-build-script for more information about build script outputs.
"#]])
.with_stderr_does_not_contain("hidden stderr")
.with_stderr_does_not_contain("[COMPILING] foo v0.0.1 ([..])")
.with_stderr_contains("[UPDATING] `[..]` index")
.with_stderr_contains("[DOWNLOADED] bar v0.0.1 ([..])")
.with_stderr_contains("[COMPILING] bar v0.0.1")
.with_stderr_contains(&format!("[ERROR] bar@0.0.1: {}", ERROR))
.with_stderr_contains(&format!("[WARNING] bar@0.0.1: {}", WARNING1))
.with_stderr_contains(&format!("[WARNING] bar@0.0.1: {}", WARNING2))
.run();
}

0 comments on commit 8c0028a

Please sign in to comment.