Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
- Fixed shell hook spawning a nested `devenv shell` when `devenv shell` was entered manually. Follow-up to [#2815](https://github.com/cachix/devenv/pull/2815).
- Fixed "zoxide: infinite loop detected" when using `zoxide init --cmd=cd fish` and `cd`-ing into a devenv project. The fish hook now defers spawning `devenv shell` to the next prompt instead of spawning inline inside the PWD event handler, so in-progress shell state never leaks into the devenv shell ([#2841](https://github.com/cachix/devenv/issues/2841)).
- Fixed stale task and option results that lingered when `devenv.nix` was edited while a command was already evaluating. The eval cache no longer stores results whose tracked input files were modified mid-evaluation, so the next run no longer needs `.devenv/nix-eval-cache.db*` to be wiped to see the new definitions ([#2745](https://github.com/cachix/devenv/issues/2745)).
- Fixed import path validation rejecting valid input-relative imports on macOS, where `TMPDIR` resolves via the `/var → /private/var` symlink (`Import path '<name>/...' resolves outside the git repository`). `validate_within_root` now canonicalizes the longest existing ancestor of the path before comparing against the root.
- Surfaced the actionable `error: …` paragraph from Nix evaluation diagnostics as the headline instead of burying it under ~100 lines of `--show-trace` frames. For example, the "use `devenv inputs add …`" suggestion that follows from a missing input in `devenv.yaml` now appears next to the "Failed to …" line; the full trace moves to the `help:` section ([#2820](https://github.com/cachix/devenv/issues/2820)).

### Improvements

Expand Down
51 changes: 36 additions & 15 deletions devenv-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1231,21 +1231,13 @@ impl Config {
&& let Some(canonical_root) = canonical_root
{
// Import path doesn't exist, but root does.
// Canonicalize the parent directory to resolve symlinks
// (e.g. /tmp -> /run/user/...), falling back to lexical
// normalization only when the parent doesn't exist either.
let abs_import = if let Some(parent) = import_path.parent() {
if let Ok(canonical_parent) = parent.canonicalize() {
canonical_parent.join(import_path.file_name().unwrap_or_default())
} else if import_path.is_absolute() {
Self::normalize_path_components(import_path)
} else if let Ok(cwd) = std::env::current_dir() {
Self::normalize_path_components(&cwd.join(import_path))
} else {
return Ok(());
}
} else if import_path.is_absolute() {
Self::normalize_path_components(import_path)
// Walk up the path looking for an existing ancestor that we can
// canonicalize (so symlinks like macOS `/var -> /private/var` get
// resolved), then append the remaining components lexically.
let abs_import = if import_path.is_absolute() {
Self::canonicalize_existing_ancestor(import_path)
} else if let Ok(cwd) = std::env::current_dir() {
Self::canonicalize_existing_ancestor(&cwd.join(import_path))
} else {
return Ok(());
};
Expand Down Expand Up @@ -1273,6 +1265,35 @@ impl Config {
Ok(())
}

/// Resolve an absolute path by canonicalizing the longest existing prefix
/// and appending the remaining components lexically. This handles paths
/// whose leaves don't exist yet while still resolving symlinks anywhere
/// up the chain (e.g. macOS `/var/folders/...` -> `/private/var/folders/...`).
fn canonicalize_existing_ancestor(path: &Path) -> PathBuf {
let mut probe = path.to_path_buf();
let mut tail: Vec<std::ffi::OsString> = Vec::new();
loop {
match probe.canonicalize() {
Ok(canonical) => {
let mut result = canonical;
for component in tail.iter().rev() {
result.push(component);
}
return result;
}
Err(_) => {
let file_name = probe.file_name().map(|n| n.to_os_string());
if !probe.pop() || file_name.is_none() {
// Nothing exists along the way; fall back to lexical
// normalization of the original path.
return Self::normalize_path_components(path);
}
tail.push(file_name.unwrap());
}
}
}
}

/// Normalizes a path by resolving `.` and `..` components without requiring the path to exist.
fn normalize_path_components(path: &Path) -> PathBuf {
let mut components = Vec::new();
Expand Down
13 changes: 0 additions & 13 deletions devenv-core/src/nix_log_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,6 @@ impl NixLogBridge {
}
}

/// Peek at stored pre-REPL errors without clearing.
///
/// Returns a clone of the error messages. Use this when you need to
/// include errors in an error message but want to keep them available
/// for later (e.g., for the debugger).
pub fn peek_pre_repl_errors(&self) -> Vec<String> {
if let Ok(errors) = self.pre_repl_errors.lock() {
errors.clone()
} else {
Vec::new()
}
}

/// Add an observer to receive operation notifications during evaluation.
///
/// Observers are notified of file/env operations (EvalOp) as they are parsed
Expand Down
16 changes: 4 additions & 12 deletions devenv-nix-backend/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ use once_cell::sync::OnceCell;
use crate::anyhow_ext::AnyhowToMiette;
use crate::build_environment::BuildEnvironment as RustBuildEnvironment;
use crate::cnix_store::CNixStore;
use crate::error::{dedent_lines, select_raw_error};
use crate::error::format_eval_error;
use crate::umask_guard::UmaskGuard;

/// Initialize Nix FFI globals, register the calling thread with the GC,
Expand Down Expand Up @@ -520,17 +520,9 @@ impl NixCBackend {
}

fn enrich_eval_error(&self, err: miette::Error, context: &str) -> miette::Error {
// Flatten into a single diagnostic. Nix already emits a complete
// tree-style trace; letting miette render the FFI cause chain on top
// of that produces deep continuation indent under `─▶` arrows.
//
// Skip warning-prefixed log entries: Nix occasionally emits warnings
// (e.g. restricted-settings notices during init) through the FFI logger
// at error verbosity, which would otherwise shadow the actual error —
// for syntax errors the real message only arrives via the FFI return.
let nix_errors = self.nix_log_bridge.peek_pre_repl_errors();
let raw = select_raw_error(&nix_errors, || format!("{err:#}"));
miette!("{context}: {}", dedent_lines(&raw))
// Delegate to the pure helper so the shape-the-Nix-output logic can
// be unit-tested with synthetic inputs (see `error::format_eval_error`).
miette::Report::from(format_eval_error(&format!("{err:#}"), context))
}

fn eval_attr_uncached(
Expand Down
Loading
Loading