From 48ee942c4fa75a61342471c788ef0ea8305d1900 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Sun, 28 Dec 2025 01:51:35 -0500 Subject: [PATCH] fix(cli): canonicalize build `--output` path This fixes a potential issue with the new lock file hashing mechanism, in which two different path literals pointing to the same location would hash to separate lock files, allowing a race condition. (cherry picked from commit 93d793d24920db614b560c251b7ddff822597f21) --- crates/cli/src/main.rs | 14 ++++++++++++-- crates/loader/src/loader.rs | 10 +++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index af4f13a7..35d8fcb7 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -955,11 +955,21 @@ impl Build { } else { let output_path = if let Some(ref path) = self.output { let path = Path::new(path); - if path.is_absolute() { + let full_path = if path.is_absolute() { path.to_path_buf() } else { current_dir.join(path) - } + }; + let parent_path = full_path + .parent() + .context("Output path must have a parent")?; + let name = full_path + .file_name() + .context("Ouput path must have a filename")?; + fs::create_dir_all(parent_path).context("Failed to create output path")?; + let mut canon_path = parent_path.canonicalize().context("Invalid output path")?; + canon_path.push(name); + canon_path } else { let file_name = grammar_path .file_stem() diff --git a/crates/loader/src/loader.rs b/crates/loader/src/loader.rs index a6f10d54..11c8b673 100644 --- a/crates/loader/src/loader.rs +++ b/crates/loader/src/loader.rs @@ -1098,11 +1098,11 @@ impl Loader { // Ensure the dynamic library exists before trying to load it. This can // happen in race conditions where we couldn't acquire the lock because - // another process was compiling but it still haven't finished by the + // another process was compiling but it still hasn't finished by the // time we reach this point, so the output file still doesn't exist. // - // Instead of complaining about library load failure in `load_language`, - // inform the user about the precise issue. + // Instead of allowing the `load_language` call below to fail, return a + // clearer error to the user here. if !output_path.exists() { let msg = format!( "Dynamic library `{}` not found after build attempt. \ @@ -1110,10 +1110,10 @@ impl Loader { output_path.display() ); - return Err(LoaderError::IO(IoError::new( + Err(LoaderError::IO(IoError::new( std::io::Error::new(std::io::ErrorKind::NotFound, msg), Some(output_path.as_path()), - ))); + )))?; } Self::load_language(&output_path, &language_fn_name)