Skip to content

Commit

Permalink
Replace executables with broken symlinks during uv python install
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Dec 10, 2024
1 parent 57a7f04 commit 56415e1
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 20 deletions.
58 changes: 38 additions & 20 deletions crates/uv/src/commands/python/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,32 +358,50 @@ pub(crate) async fn install(
target.simplified_display()
);

// Check if the existing link is valid
let valid_link = target
.read_link()
.and_then(|target| target.try_exists())
.inspect_err(|err| debug!("Failed to inspect executable with error: {err}"))
.unwrap_or(true);

// Figure out what installation it references, if any
let existing = find_matching_bin_link(
installations
.iter()
.copied()
.chain(existing_installations.iter()),
&target,
);
let existing = valid_link
.then(|| {
find_matching_bin_link(
installations
.iter()
.copied()
.chain(existing_installations.iter()),
&target,
)
})
.flatten();

match existing {
None => {
// There's an existing executable we don't manage, require `--force`
if !force {
errors.push((
installation.key(),
anyhow::anyhow!(
"Executable already exists at `{}` but is not managed by uv; use `--force` to replace it",
to.simplified_display()
),
));
continue;
if valid_link {
if !force {
errors.push((
installation.key(),
anyhow::anyhow!(
"Executable already exists at `{}` but is not managed by uv; use `--force` to replace it",
to.simplified_display()
),
));
continue;
}
debug!(
"Replacing existing executable at `{}` due to `--force`",
target.simplified_display()
);
} else {
debug!(
"Replacing broken symlink at `{}`",
target.simplified_display()
);
}
debug!(
"Replacing existing executable at `{}` due to `--force`",
target.simplified_display()
);
}
Some(existing) if existing == *installation => {
// The existing link points to the same installation, so we're done unless
Expand Down
36 changes: 36 additions & 0 deletions crates/uv/tests/it/python_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,3 +839,39 @@ fn python_install_unknown() {
error: `./foo` is not a valid Python download request; see `uv python help` for supported formats and `uv python list --only-downloads` for available versions
"###);
}

#[cfg(unix)]
fn python_install_preview_broken_link() {
use assert_fs::prelude::PathCreateDir;
use fs_err::os::unix::fs::symlink;

let context: TestContext = TestContext::new_with_versions(&[])
.with_filtered_python_keys()
.with_filtered_exe_suffix();

let bin_python = context.temp_dir.child("bin").child("python3.13");

// Create a broken symlink
context.temp_dir.child("bin").create_dir_all().unwrap();
symlink(context.temp_dir.join("does-not-exist"), &bin_python).unwrap();

// Install
uv_snapshot!(context.filters(), context.python_install().arg("--preview").arg("3.13"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Installed Python 3.13.1 in [TIME]
+ cpython-3.13.1-[PLATFORM] (python3.13)
"###);

// We should replace the broken symlink
insta::with_settings!({
filters => context.filters(),
}, {
insta::assert_snapshot!(
read_link_path(&bin_python), @"[TEMP_DIR]/managed/cpython-3.13.1-[PLATFORM]/bin/python3.13"
);
});
}

0 comments on commit 56415e1

Please sign in to comment.