Avoid overwriting symlinks in pip compile output (#6487)

## Summary

Closes https://github.com/astral-sh/uv/issues/6485.
This commit is contained in:
Charlie Marsh 2024-08-22 22:54:45 -04:00 committed by GitHub
parent 1cd80139af
commit 9b42142fe7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 0 deletions

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::env;
use std::io::stdout;
use std::path::Path;
@ -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?;
}

View file

@ -11907,3 +11907,54 @@ 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());
Ok(())
}