Skip to content

Commit

Permalink
Avoid overwriting symlinks in pip compile output
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Aug 23, 2024
1 parent 1cd8013 commit f2e5367
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 0 deletions.
5 changes: 5 additions & 0 deletions crates/uv/src/commands/pip/compile.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::env;
use std::io::stdout;
use std::path::Path;
Expand Down Expand Up @@ -643,6 +644,10 @@ impl<'a> OutputWriter<'a> {
/// Commit the buffer to the output file.
async fn commit(self) -> std::io::Result<()> {
if let Some(output_file) = self.output_file {
// If the output file is an existing symlink, write to the destination instead.
let output_file = fs_err::read_link(output_file)
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed(output_file));
let stream = anstream::adapter::strip_bytes(&self.buffer).into_vec();
uv_fs::write_atomic(output_file, &stream).await?;
}
Expand Down
56 changes: 56 additions & 0 deletions crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11907,3 +11907,59 @@ fn invalid_extra() -> Result<()> {

Ok(())
}

/// Respect symlinks of output files.
#[test]
#[cfg(not(windows))]
fn symlink() -> Result<()> {
let context = TestContext::new("3.8");
let requirements_in = context.temp_dir.child("requirements.in");
requirements_in.write_str("anyio")?;

// Create an output file.
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio")?;

// Create a symlink to the output file.
let symlink = context.temp_dir.child("requirements-symlink.txt");
symlink.symlink_to_file(requirements_txt.path())?;

// Write to the symlink.
uv_snapshot!(context.pip_compile()
.arg("requirements.in")
.arg("--output-file")
.arg("requirements-symlink.txt"), @r###"
success: true
exit_code: 0
----- stdout -----
# This file was autogenerated by uv via the following command:
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --output-file requirements-symlink.txt
anyio==4.3.0
# via -r requirements.in
exceptiongroup==1.2.0
# via anyio
idna==3.6
# via anyio
sniffio==1.3.1
# via anyio
typing-extensions==4.10.0
# via anyio
----- stderr -----
Resolved 5 packages in [TIME]
"###
);

// The symlink should still be a symlink.
assert!(symlink.path().symlink_metadata()?.file_type().is_symlink());

// The destination of the symlink should be the same as the output file.
assert_eq!(symlink.path().read_link()?, requirements_txt.path());

// The symlink should contain the same content as the output file.
let symlink = fs_err::read_to_string(symlink.path())?;
let requirements_txt = fs_err::read_to_string(requirements_txt.path())?;
assert_eq!(symlink, requirements_txt);

Ok(())
}

0 comments on commit f2e5367

Please sign in to comment.