From 3bff084b3bc8ca342ea6ac5e9ec272f754673547 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 26 Oct 2025 19:37:13 +0000 Subject: [PATCH] feat: display nix stacktraces --- --- a/cmds/fleet/src/cmds/build_systems.rs +++ b/cmds/fleet/src/cmds/build_systems.rs @@ -118,7 +118,7 @@ { Ok(path) => path, Err(e) => { - error!("failed to build host system closure: {:#}", e); + error!("failed to build host system closure: {:?}", e); return; } }; --- a/crates/nix-eval/build.rs +++ b/crates/nix-eval/build.rs @@ -16,6 +16,7 @@ // Link nix C++ libraries for cxx for lib in &[ "nix-util", + "nix-util-c", "nix-store", "nix-expr", "nix-flake", @@ -34,12 +35,12 @@ cxx_build::bridge("src/logging.rs") .file("src/logging.cc") - .std("c++20") + .std("c++23") .shared_flag(true) .compile("nix-eval-logging"); cxx_build::bridge("src/lib.rs") .file("src/lib.cc") - .std("c++20") + .std("c++23") .shared_flag(true) .compile("nix-eval"); --- a/crates/nix-eval/src/lib.rs +++ b/crates/nix-eval/src/lib.rs @@ -13,7 +13,7 @@ pub use anyhow::Result; use tracing::instrument; -use self::logging::nix_logging_cxx; +use self::logging::{ErrorInfoBuilder, nix_logging_cxx}; use self::nix_cxx::set_fetcher_setting; use self::nix_raw::{ BindingsBuilder as c_bindings_builder, EvalState as c_eval_state, GC_SUCCESS, @@ -179,8 +179,9 @@ let code = unsafe { err_code(self.0) }; NixErrorKind::from_int(code) } - fn error<'t>(&self) -> Option> { + fn error<'t>(&self) -> Option<(Cow<'t, str>, Option>)> { if let NixErrorKind::Generic = self.error_kind()? { + let ei = unsafe { logging::nix_logging_cxx::extract_error_info(self.0) }; let mut err_out = String::new(); unsafe { err_info_msg( @@ -190,13 +191,13 @@ (&raw mut err_out).cast(), ) }; - return Some(Cow::Owned(err_out)); + return Some((Cow::Owned(err_out), Some(ei))); }; // TODO: Can throw error (resulting in panic) if unable to retrieve error. Should be able to resolve by passing context as a first argument, // but it looks ugly let str = unsafe { err_msg(null_mut(), self.0, null_mut()) }; - Some(unsafe { CStr::from_ptr(str) }.to_string_lossy()) + Some((unsafe { CStr::from_ptr(str) }.to_string_lossy(), None)) } fn clean_err(&mut self) { unsafe { @@ -205,8 +206,20 @@ } fn bail_if_error(&self) -> Result<()> { - if let Some(err) = self.error() { - bail!("{err}"); + if let Some((err, stack)) = self.error() { + let mut e = Err(anyhow!("{err}")); + if let Some(stack) = stack { + for ele in stack.stack_frames { + e = e.with_context(|| { + if ele.pos.is_empty() { + ele.msg + } else { + format!("{} at {}", ele.msg, ele.pos) + } + }) + } + } + return e.context(""); }; Ok(()) } --- a/crates/nix-eval/src/logging.cc +++ b/crates/nix-eval/src/logging.cc @@ -1,9 +1,36 @@ -#include "nix-eval/src/logging.rs" #include "logging.hh" #include +#include using namespace nix; +rust::Box copy_error_info(const ErrorInfo &ei) { + auto s = ei.msg.str(); + rust::Slice str( + reinterpret_cast(s.data()), s.size()); + auto b = new_error_info(ei.level, str); + if (!ei.traces.empty()) { + for (auto iter = ei.traces.rbegin(); iter != ei.traces.rend(); ++iter) { + auto msg = iter->hint.str(); + + rust::Slice msgv( + reinterpret_cast(msg.data()), msg.size()); + + std::ostringstream oss; + if (iter->pos) { + iter->pos->print(oss, true); + } + std::string pos = oss.str(); + + rust::Slice posv( + reinterpret_cast(pos.data()), pos.size()); + + b->push_stack_frame(msgv, posv); + } + } + return b; +} + struct TracingLogger : Logger { TracingLogger() {} @@ -14,10 +41,8 @@ emit_log(lvl, str); } void logEI(const ErrorInfo &ei) override { - auto s = ei.msg.str(); - rust::Slice str( - reinterpret_cast(s.data()), s.size()); - emit_log(ei.level, str); + auto b = copy_error_info(ei); + b->emit_error_info(); } void startActivity(ActivityId act, Verbosity lvl, ActivityType type, @@ -74,4 +99,8 @@ logger = std::make_unique(); // verbosity = lvlVomit; } +rust::Box +extract_error_info(const nix_c_context *read_context) { + return copy_error_info(read_context->info.value()); +} } --- a/crates/nix-eval/src/logging.hh +++ b/crates/nix-eval/src/logging.hh @@ -1,5 +1,12 @@ #pragma once +#include "nix-eval/src/logging.rs" +#include "rust/cxx.h" +#include +#include + +struct ErrorInfoBuilder; extern "C" { void apply_tracing_logger(); +rust::Box extract_error_info(const nix_c_context *ctx); } --- a/crates/nix-eval/src/logging.rs +++ b/crates/nix-eval/src/logging.rs @@ -2,6 +2,7 @@ use std::fmt::Arguments; use std::sync::{LazyLock, Mutex}; +use cxx::ExternType; use tracing::{ Level, Span, debug, debug_span, error, error_span, info, info_span, trace, trace_span, warn, warn_span, @@ -535,6 +536,45 @@ out.output } +#[derive(Debug)] +pub struct StackFrame { + pub msg: String, + pub pos: String, +} + +#[derive(Debug)] +pub struct ErrorInfoBuilder { + level: Level, + msg: String, + pub stack_frames: Vec, +} +fn new_error_info(lvl: u32, v: &[u8]) -> Box { + let verbosity = Verbosity::from_int(lvl); + let level: Level = verbosity.into(); + let v = String::from_utf8_lossy(v); + Box::new(ErrorInfoBuilder { + level, + msg: v.to_string(), + stack_frames: Vec::new(), + }) +} +impl ErrorInfoBuilder { + fn push_stack_frame(&mut self, v: &[u8], pos: &[u8]) { + let v = String::from_utf8_lossy(v); + let pos = String::from_utf8_lossy(pos); + self.stack_frames.push(StackFrame { + msg: v.to_string(), + pos: pos.to_string(), + }); + } + fn emit_error_info(&mut self) { + error!("{}", self.msg); + for frame in &self.stack_frames { + error!(" {} at {}", frame.msg, frame.pos) + } + } +} + #[cxx::bridge] pub mod nix_logging_cxx { extern "Rust" { @@ -544,7 +584,14 @@ fn add_string_field(&mut self, v: &[u8]); fn emit(&mut self, parent: u64, s: &str); fn emit_result(&mut self, ty: u32); - + } + extern "Rust" { + type ErrorInfoBuilder; + fn new_error_info(lvl: u32, v: &[u8]) -> Box; + fn push_stack_frame(&mut self, v: &[u8], pos: &[u8]); + fn emit_error_info(&mut self); + } + extern "Rust" { fn emit_warn(v: &str); fn emit_stop(id: u64); fn emit_log(lvl: u32, v: &[u8]); @@ -552,6 +599,15 @@ unsafe extern "C++" { include!("nix-eval/src/logging.hh"); + type nix_c_context = crate::nix_raw::c_context; + fn apply_tracing_logger(); + unsafe fn extract_error_info(ctx: *const nix_c_context) -> Box; } } + +unsafe impl ExternType for crate::nix_raw::c_context { + type Id = cxx::type_id!("nix_c_context"); + + type Kind = cxx::kind::Opaque; +} -- gitstuff