Skip to content

Commit

Permalink
Merge branch 'master' into bump-backtrace-rs
Browse files Browse the repository at this point in the history
  • Loading branch information
ten3roberts committed Jul 20, 2024
2 parents 254309f + df42dc4 commit a6c8a61
Show file tree
Hide file tree
Showing 16 changed files with 236 additions and 271 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ rust-version = "1.65.0"
indenter = "0.3.0"
once_cell = "1.18.0"
owo-colors = "4.0"
autocfg = "1.0"

[profile.dev.package.backtrace]
opt-level = 3
Expand Down
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,12 @@ avoid using `eyre::Report` as your public error type.
}
```

- If using the nightly channel, a backtrace is captured and printed with the
error if the underlying error type does not already provide its own. In order
to see backtraces, they must be enabled through the environment variables
- If using rust >1.65, a backtrace is captured and printed with the
error.

On nightly eyre will use the underlying error's backtrace if it has one.

In order to see backtraces, they must be enabled through the environment variables
described in [`std::backtrace`]:

- If you want panics and errors to both have backtraces, set
Expand All @@ -141,7 +144,7 @@ avoid using `eyre::Report` as your public error type.
- Eyre works with any error type that has an impl of `std::error::Error`,
including ones defined in your crate. We do not bundle a `derive(Error)` macro
but you can write the impls yourself or use a standalone macro like
[thiserror].
[thiserror](https://github.com/dtolnay/thiserror).

```rust
use thiserror::Error;
Expand Down Expand Up @@ -178,6 +181,15 @@ No-std support was removed in 2020 in [commit 608a16a] due to unaddressed upstre
[commit 608a16a]:
https://github.com/eyre-rs/eyre/pull/29/commits/608a16aa2c2c27eca6c88001cc94c6973c18f1d5


## Backtrace support

The built in default handler has support for capturing backtrace using `rustc-1.65` or later.

Backtraces are captured when an error is converted to an `eyre::Report` (such as using `?` or `eyre!`).

If using the nightly toolchain, backtraces will also be captured and accessed from other errors using [error_generic_member_access](https://github.com/rust-lang/rfcs/pull/2895) if available.

## Comparison to failure

The `eyre::Report` type works something like `failure::Error`, but unlike
Expand All @@ -195,8 +207,6 @@ you need an error type that can be handled via match or reported. This is
common in library crates where you don't know how your users will handle
your errors.

[thiserror]: https://github.com/dtolnay/thiserror

## Compatibility with `anyhow`

This crate does its best to be usable as a drop in replacement of `anyhow` and
Expand Down
33 changes: 0 additions & 33 deletions color-eyre/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,6 @@ use std::env;
use std::fmt::Write as _;
use std::{fmt, path::PathBuf, sync::Arc};

#[derive(Debug)]
struct InstallError;

impl fmt::Display for InstallError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("could not install the BacktracePrinter as another was already installed")
}
}

impl std::error::Error for InstallError {}

#[derive(Debug)]
struct InstallThemeError;

impl fmt::Display for InstallThemeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("could not set the provided `Theme` globally as another was already set")
}
}

impl std::error::Error for InstallThemeError {}

#[derive(Debug)]
struct InstallColorSpantraceThemeError;

impl fmt::Display for InstallColorSpantraceThemeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("could not set the provided `Theme` via `color_spantrace::set_theme` globally as another was already set")
}
}

impl std::error::Error for InstallColorSpantraceThemeError {}

/// A struct that represents a theme that is used by `color_eyre`
#[derive(Debug, Copy, Clone, Default)]
pub struct Theme {
Expand Down
8 changes: 8 additions & 0 deletions color-eyre/src/writers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ impl<W> WriterExt for W {
}
}

#[cfg(feature = "issue-url")]
pub(crate) trait DisplayExt: Sized + Display {
fn with_header<H: Display>(self, header: H) -> Header<Self, H>;
fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F>;
}

#[cfg(feature = "issue-url")]
impl<T> DisplayExt for T
where
T: Display,
Expand Down Expand Up @@ -80,11 +82,13 @@ where
}
}

#[cfg(feature = "issue-url")]
pub(crate) struct FooterWriter<W> {
inner: W,
had_output: bool,
}

#[cfg(feature = "issue-url")]
impl<W> fmt::Write for FooterWriter<W>
where
W: fmt::Write,
Expand All @@ -98,6 +102,7 @@ where
}
}

#[cfg(feature = "issue-url")]
#[allow(explicit_outlives_requirements)]
pub(crate) struct Footer<B, H>
where
Expand All @@ -108,6 +113,7 @@ where
footer: H,
}

#[cfg(feature = "issue-url")]
impl<B, H> fmt::Display for Footer<B, H>
where
B: Display,
Expand All @@ -129,6 +135,7 @@ where
}
}

#[cfg(feature = "issue-url")]
#[allow(explicit_outlives_requirements)]
pub(crate) struct Header<B, H>
where
Expand All @@ -139,6 +146,7 @@ where
h: H,
}

#[cfg(feature = "issue-url")]
impl<B, H> fmt::Display for Header<B, H>
where
B: Display,
Expand Down
7 changes: 6 additions & 1 deletion color-eyre/tests/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,12 @@ fn test_backwards_compatibility(target: String, file_name: &str) {
fn normalize_backtrace(input: &str) -> String {
input
.lines()
.take_while(|v| !v.contains("core::panic") && !v.contains("theme_test_helper::main"))
.take_while(|v| {
!v.contains("core::panic")
&& !v.contains("theme_test_helper::main")
&& !v.contains("theme::test_error_backwards_compatibility::closure")
&& !v.contains("theme::test_error_backwards_compatibility::{{closure}}")
})
.collect::<Vec<_>>()
.join("\n")
}
Expand Down
3 changes: 3 additions & 0 deletions eyre/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ indenter = { workspace = true }
once_cell = { workspace = true }
pyo3 = { version = "0.20", optional = true, default-features = false }

[build-dependencies]
autocfg = { workspace = true }

[dev-dependencies]
futures = { version = "0.3", default-features = false }
rustversion = "1.0"
Expand Down
171 changes: 72 additions & 99 deletions eyre/build.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::Path;
use std::process::{Command, ExitStatus};
use std::str;

// This code exercises the surface area that we expect of the std Backtrace
// type. If the current toolchain is able to compile it, we go ahead and use
// backtrace in eyre.
const BACKTRACE_PROBE: &str = r#"
#![feature(backtrace)]
use std::{
env, fs,
path::Path,
process::{Command, ExitStatus},
};

fn main() {
let ac = autocfg::new();

// https://github.com/rust-lang/rust/issues/99301 [nightly]
//
// Autocfg does currently not support custom probes, or `nightly` only features
match compile_probe(GENERIC_MEMBER_ACCESS_PROBE) {
Some(status) if status.success() => autocfg::emit("generic_member_access"),
_ => {}
}

// https://github.com/rust-lang/rust/issues/47809 [rustc-1.46]
ac.emit_expression_cfg("std::panic::Location::caller", "track_caller");

if ac.probe_rustc_version(1, 52) {
autocfg::emit("eyre_no_fmt_arguments_as_str");
}

if ac.probe_rustc_version(1, 58) {
autocfg::emit("eyre_no_fmt_args_capture");
}

if ac.probe_rustc_version(1, 65) {
autocfg::emit("backtrace")
}
}

// This code exercises the surface area or the generic member access feature for the `std::error::Error` trait.
//
// This is use to detect and supply backtrace information through different errors types.
const GENERIC_MEMBER_ACCESS_PROBE: &str = r#"
#![feature(error_generic_member_access)]
#![allow(dead_code)]
use std::backtrace::{Backtrace, BacktraceStatus};
use std::error::Error;
use std::error::{Error, Request};
use std::fmt::{self, Display};
#[derive(Debug)]
struct E;
struct E {
backtrace: MyBacktrace,
}
#[derive(Debug)]
struct MyBacktrace;
impl Display for E {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
Expand All @@ -26,59 +56,44 @@ const BACKTRACE_PROBE: &str = r#"
}
impl Error for E {
fn backtrace(&self) -> Option<&Backtrace> {
let backtrace = Backtrace::capture();
match backtrace.status() {
BacktraceStatus::Captured | BacktraceStatus::Disabled | _ => {}
}
unimplemented!()
fn provide<'a>(&'a self, request: &mut Request<'a>) {
request
.provide_ref::<MyBacktrace>(&self.backtrace);
}
}
"#;

const TRACK_CALLER_PROBE: &str = r#"
#![allow(dead_code)]
#[track_caller]
fn foo() {
let _location = std::panic::Location::caller();
}
"#;

fn main() {
match compile_probe(BACKTRACE_PROBE) {
Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"),
_ => {}
}

match compile_probe(TRACK_CALLER_PROBE) {
Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"),
_ => {}
}
fn compile_probe(probe: &str) -> Option<ExitStatus> {
let rustc = env::var_os("RUSTC")?;
let out_dir = env::var_os("OUT_DIR")?;
let probefile = Path::new(&out_dir).join("probe.rs");
fs::write(&probefile, probe).ok()?;

let version = match rustc_version_info() {
Some(version) => version,
None => return,
};
let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty());
let rustc_workspace_wrapper =
env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty());
let mut rustc = rustc_wrapper
.into_iter()
.chain(rustc_workspace_wrapper)
.chain(std::iter::once(rustc));

version.toolchain.set_feature();
let mut cmd = Command::new(rustc.next().unwrap());
cmd.args(rustc);

if version.minor < 52 {
println!("cargo:rustc-cfg=eyre_no_fmt_arguments_as_str");
if let Some(target) = env::var_os("TARGET") {
cmd.arg("--target").arg(target);
}

if version.minor < 58 {
println!("cargo:rustc-cfg=eyre_no_fmt_args_capture");
// If Cargo wants to set RUSTFLAGS, use that.
if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") {
if !rustflags.is_empty() {
for arg in rustflags.split('\x1f') {
cmd.arg(arg);
}
}
}
}

fn compile_probe(probe: &str) -> Option<ExitStatus> {
let rustc = env::var_os("RUSTC")?;
let out_dir = env::var_os("OUT_DIR")?;
let probefile = Path::new(&out_dir).join("probe.rs");
fs::write(&probefile, probe).ok()?;
Command::new(rustc)
.arg("--edition=2018")
cmd.arg("--edition=2018")
.arg("--crate-name=eyre_build")
.arg("--crate-type=lib")
.arg("--emit=metadata")
Expand All @@ -88,45 +103,3 @@ fn compile_probe(probe: &str) -> Option<ExitStatus> {
.status()
.ok()
}

// TODO factor this toolchain parsing and related tests into its own file
#[derive(PartialEq)]
enum Toolchain {
Stable,
Beta,
Nightly,
}
impl Toolchain {
fn set_feature(self) {
match self {
Toolchain::Nightly => println!("cargo:rustc-cfg=nightly"),
Toolchain::Beta => println!("cargo:rustc-cfg=beta"),
Toolchain::Stable => println!("cargo:rustc-cfg=stable"),
}
}
}

struct VersionInfo {
minor: u32,
toolchain: Toolchain,
}

fn rustc_version_info() -> Option<VersionInfo> {
let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc"));
let output = Command::new(rustc).arg("--version").output().ok()?;
let version = str::from_utf8(&output.stdout).ok()?;
let mut pieces = version.split(['.', ' ', '-']);
if pieces.next() != Some("rustc") {
return None;
}
let _major: u32 = pieces.next()?.parse().ok()?;
let minor = pieces.next()?.parse().ok()?;
let _patch: u32 = pieces.next()?.parse().ok()?;
let toolchain = match pieces.next() {
Some("beta") => Toolchain::Beta,
Some("nightly") => Toolchain::Nightly,
_ => Toolchain::Stable,
};
let version = VersionInfo { minor, toolchain };
Some(version)
}
Loading

0 comments on commit a6c8a61

Please sign in to comment.