Skip to content

Commit 9b42142

Browse files
Avoid overwriting symlinks in pip compile output (#6487)
## Summary Closes #6485.
1 parent 1cd8013 commit 9b42142

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

crates/uv/src/commands/pip/compile.rs

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::env;
23
use std::io::stdout;
34
use std::path::Path;
@@ -643,6 +644,10 @@ impl<'a> OutputWriter<'a> {
643644
/// Commit the buffer to the output file.
644645
async fn commit(self) -> std::io::Result<()> {
645646
if let Some(output_file) = self.output_file {
647+
// If the output file is an existing symlink, write to the destination instead.
648+
let output_file = fs_err::read_link(output_file)
649+
.map(Cow::Owned)
650+
.unwrap_or(Cow::Borrowed(output_file));
646651
let stream = anstream::adapter::strip_bytes(&self.buffer).into_vec();
647652
uv_fs::write_atomic(output_file, &stream).await?;
648653
}

crates/uv/tests/pip_compile.rs

+51
Original file line numberDiff line numberDiff line change
@@ -11907,3 +11907,54 @@ fn invalid_extra() -> Result<()> {
1190711907

1190811908
Ok(())
1190911909
}
11910+
11911+
/// Respect symlinks of output files.
11912+
#[test]
11913+
#[cfg(not(windows))]
11914+
fn symlink() -> Result<()> {
11915+
let context = TestContext::new("3.8");
11916+
let requirements_in = context.temp_dir.child("requirements.in");
11917+
requirements_in.write_str("anyio")?;
11918+
11919+
// Create an output file.
11920+
let requirements_txt = context.temp_dir.child("requirements.txt");
11921+
requirements_txt.write_str("anyio")?;
11922+
11923+
// Create a symlink to the output file.
11924+
let symlink = context.temp_dir.child("requirements-symlink.txt");
11925+
symlink.symlink_to_file(requirements_txt.path())?;
11926+
11927+
// Write to the symlink.
11928+
uv_snapshot!(context.pip_compile()
11929+
.arg("requirements.in")
11930+
.arg("--output-file")
11931+
.arg("requirements-symlink.txt"), @r###"
11932+
success: true
11933+
exit_code: 0
11934+
----- stdout -----
11935+
# This file was autogenerated by uv via the following command:
11936+
# uv pip compile --cache-dir [CACHE_DIR] requirements.in --output-file requirements-symlink.txt
11937+
anyio==4.3.0
11938+
# via -r requirements.in
11939+
exceptiongroup==1.2.0
11940+
# via anyio
11941+
idna==3.6
11942+
# via anyio
11943+
sniffio==1.3.1
11944+
# via anyio
11945+
typing-extensions==4.10.0
11946+
# via anyio
11947+
11948+
----- stderr -----
11949+
Resolved 5 packages in [TIME]
11950+
"###
11951+
);
11952+
11953+
// The symlink should still be a symlink.
11954+
assert!(symlink.path().symlink_metadata()?.file_type().is_symlink());
11955+
11956+
// The destination of the symlink should be the same as the output file.
11957+
assert_eq!(symlink.path().read_link()?, requirements_txt.path());
11958+
11959+
Ok(())
11960+
}

0 commit comments

Comments
 (0)