mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 02:48:17 +00:00
Replace executables with broken symlinks during uv python install
(#9706)
I somehow got in a state where we'd fail to install with ``` error: Failed to install cpython-3.13.0-macos-aarch64-none Caused by: Executable already exists at `/Users/zb/.local/bin/python3` but is not managed by uv; use `--force` to replace it error: Failed to install cpython-3.13.0-macos-aarch64-none Caused by: Executable already exists at `/Users/zb/.local/bin/python` but is not managed by uv; use `--force` to replace it ``` but `python` / `python3` _were_ managed by uv, they just were linked to an installation that was deleted. This updates the logic to replace broken executables that are broken symlinks. We apply this to broken links regardless of whether or not we think the target is managed by uv.
This commit is contained in:
parent
57a7f04f9a
commit
589416183f
2 changed files with 75 additions and 20 deletions
|
@ -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
|
||||
|
|
|
@ -839,3 +839,40 @@ 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)]
|
||||
#[test]
|
||||
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"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue