git.delta.rocks / jrsonnet / refs/commits / 3bff084b3bc8

difftreelog

feat display nix stacktraces

urvrmlrlYaroslav Bolyukin2025-10-26parent: #f5a8281.patch.diff
in: trunk

6 files changed

modifiedcmds/fleet/src/cmds/build_systems.rsdiffbeforeafterboth
--- 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;
 						}
 					};
modifiedcrates/nix-eval/build.rsdiffbeforeafterboth
before · crates/nix-eval/build.rs
1use bindgen::{2	RustEdition,3	callbacks::{ItemInfo, ParseCallbacks},4};5use std::path::PathBuf;67#[derive(Debug)]8struct StripPrefix;9impl ParseCallbacks for StripPrefix {10	fn item_name(&self, name: ItemInfo<'_>) -> Option<String> {11		name.name.strip_prefix("nix_").map(ToOwned::to_owned)12	}13}1415fn main() {16	// Link nix C++ libraries for cxx17	for lib in &[18		"nix-util",19		"nix-store",20		"nix-expr",21		"nix-flake",22		"nix-fetchers",23		"bdw-gc",24	] {25		if let Ok(library) = pkg_config::probe_library(lib) {26			for lib_path in library.libs {27				println!("cargo:rustc-link-lib={lib_path}");28			}29			for search_path in library.link_paths {30				println!("cargo:rustc-link-search=native={}", search_path.display());31			}32		}33	}3435	cxx_build::bridge("src/logging.rs")36		.file("src/logging.cc")37		.std("c++20")38		.shared_flag(true)39		.compile("nix-eval-logging");40	cxx_build::bridge("src/lib.rs")41		.file("src/lib.cc")42		.std("c++20")43		.shared_flag(true)44		.compile("nix-eval");4546	println!("cargo:rerun-if-changed=src/lib.cc");47	println!("cargo:rerun-if-changed=src/lib.hh");48	println!("cargo:rerun-if-changed=src/logging.cc");49	println!("cargo:rerun-if-changed=src/logging.hh");5051	//52	let mut libnix = bindgen::builder()53		.rust_edition(RustEdition::Edition2024)54		.header_contents(55			"nix.h",56			"57				#define GC_THREADS58				#include <gc/gc.h>59				#include <nix_api_expr.h>60				#include <nix_api_store.h>61				#include <nix_api_flake.h>62				#include <nix_api_fetchers.h>63				#include <nix_api_util.h>64				#include <nix_api_value.h>65			",66		)67		.parse_callbacks(Box::new(StripPrefix));6869	for header in pkg_config::probe_library("nix-expr-c")70		.expect("nix-expr-c")71		.include_paths72		.into_iter()73		.chain(74			pkg_config::probe_library("nix-flake-c")75				.expect("nix-flake-c")76				.include_paths77				.into_iter(),78		)79		.chain(80			pkg_config::probe_library("nix-fetchers-c")81				.expect("nix-fetchers-c")82				.include_paths83				.into_iter(),84		)85		.chain(86			pkg_config::probe_library("bdw-gc")87				.expect("bdw-gc")88				.include_paths89				.into_iter(),90		) {91		libnix = libnix.clang_arg(format!("-I{}", header.to_str().expect("path is utf-8")));92	}9394	let mut out = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR is set by cargo"));95	out.push("bindings.rs");96	libnix97		.generate()98		.expect("generate bindings")99		.write_to_file(out)100		.expect("write bindings");101}
after · crates/nix-eval/build.rs
1use bindgen::{2	RustEdition,3	callbacks::{ItemInfo, ParseCallbacks},4};5use std::path::PathBuf;67#[derive(Debug)]8struct StripPrefix;9impl ParseCallbacks for StripPrefix {10	fn item_name(&self, name: ItemInfo<'_>) -> Option<String> {11		name.name.strip_prefix("nix_").map(ToOwned::to_owned)12	}13}1415fn main() {16	// Link nix C++ libraries for cxx17	for lib in &[18		"nix-util",19		"nix-util-c",20		"nix-store",21		"nix-expr",22		"nix-flake",23		"nix-fetchers",24		"bdw-gc",25	] {26		if let Ok(library) = pkg_config::probe_library(lib) {27			for lib_path in library.libs {28				println!("cargo:rustc-link-lib={lib_path}");29			}30			for search_path in library.link_paths {31				println!("cargo:rustc-link-search=native={}", search_path.display());32			}33		}34	}3536	cxx_build::bridge("src/logging.rs")37		.file("src/logging.cc")38		.std("c++23")39		.shared_flag(true)40		.compile("nix-eval-logging");41	cxx_build::bridge("src/lib.rs")42		.file("src/lib.cc")43		.std("c++23")44		.shared_flag(true)45		.compile("nix-eval");4647	println!("cargo:rerun-if-changed=src/lib.cc");48	println!("cargo:rerun-if-changed=src/lib.hh");49	println!("cargo:rerun-if-changed=src/logging.cc");50	println!("cargo:rerun-if-changed=src/logging.hh");5152	//53	let mut libnix = bindgen::builder()54		.rust_edition(RustEdition::Edition2024)55		.header_contents(56			"nix.h",57			"58				#define GC_THREADS59				#include <gc/gc.h>60				#include <nix_api_expr.h>61				#include <nix_api_store.h>62				#include <nix_api_flake.h>63				#include <nix_api_fetchers.h>64				#include <nix_api_util.h>65				#include <nix_api_value.h>66			",67		)68		.parse_callbacks(Box::new(StripPrefix));6970	for header in pkg_config::probe_library("nix-expr-c")71		.expect("nix-expr-c")72		.include_paths73		.into_iter()74		.chain(75			pkg_config::probe_library("nix-flake-c")76				.expect("nix-flake-c")77				.include_paths78				.into_iter(),79		)80		.chain(81			pkg_config::probe_library("nix-fetchers-c")82				.expect("nix-fetchers-c")83				.include_paths84				.into_iter(),85		)86		.chain(87			pkg_config::probe_library("bdw-gc")88				.expect("bdw-gc")89				.include_paths90				.into_iter(),91		) {92		libnix = libnix.clang_arg(format!("-I{}", header.to_str().expect("path is utf-8")));93	}9495	let mut out = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR is set by cargo"));96	out.push("bindings.rs");97	libnix98		.generate()99		.expect("generate bindings")100		.write_to_file(out)101		.expect("write bindings");102}
modifiedcrates/nix-eval/src/lib.rsdiffbeforeafterboth
--- 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<Cow<'t, str>> {
+	fn error<'t>(&self) -> Option<(Cow<'t, str>, Option<Box<ErrorInfoBuilder>>)> {
 		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("<nix frames>");
 		};
 		Ok(())
 	}
modifiedcrates/nix-eval/src/logging.ccdiffbeforeafterboth
--- 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 <nix/util/logging.hh>
+#include <nix/util/position.hh>
 
 using namespace nix;
 
+rust::Box<ErrorInfoBuilder> copy_error_info(const ErrorInfo &ei) {
+  auto s = ei.msg.str();
+  rust::Slice<const unsigned char> str(
+      reinterpret_cast<const unsigned char *>(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<const unsigned char> msgv(
+          reinterpret_cast<const unsigned char *>(msg.data()), msg.size());
+
+      std::ostringstream oss;
+      if (iter->pos) {
+        iter->pos->print(oss, true);
+      }
+      std::string pos = oss.str();
+
+      rust::Slice<const unsigned char> posv(
+          reinterpret_cast<const unsigned char *>(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<const unsigned char> str(
-        reinterpret_cast<const unsigned char *>(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<TracingLogger>();
   // verbosity = lvlVomit;
 }
+rust::Box<ErrorInfoBuilder>
+extract_error_info(const nix_c_context *read_context) {
+  return copy_error_info(read_context->info.value());
+}
 }
modifiedcrates/nix-eval/src/logging.hhdiffbeforeafterboth
--- 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 <nix_api_util.h>
+#include <nix_api_util_internal.h>
+
+struct ErrorInfoBuilder;
 
 extern "C" {
 void apply_tracing_logger();
+rust::Box<ErrorInfoBuilder> extract_error_info(const nix_c_context *ctx);
 }
modifiedcrates/nix-eval/src/logging.rsdiffbeforeafterboth
--- 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<StackFrame>,
+}
+fn new_error_info(lvl: u32, v: &[u8]) -> Box<ErrorInfoBuilder> {
+	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<ErrorInfoBuilder>;
+		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<ErrorInfoBuilder>;
 	}
 }
+
+unsafe impl ExternType for crate::nix_raw::c_context {
+	type Id = cxx::type_id!("nix_c_context");
+
+	type Kind = cxx::kind::Opaque;
+}