From 408c6110f162e60b5622f051a2177edec088fb76 Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 19 Sep 2024 06:49:14 -0500 Subject: [PATCH] Improve invalid environment warning messages --- crates/uv-tool/src/lib.rs | 18 ++++- crates/uv/src/commands/project/mod.rs | 17 ++++- crates/uv/tests/sync.rs | 105 +++++++++++++++++++++++++- 3 files changed, 131 insertions(+), 9 deletions(-) diff --git a/crates/uv-tool/src/lib.rs b/crates/uv-tool/src/lib.rs index 923dc2514a9b..86c7029cd287 100644 --- a/crates/uv-tool/src/lib.rs +++ b/crates/uv-tool/src/lib.rs @@ -213,10 +213,20 @@ impl InstalledTools { Err(uv_python::Error::Query(uv_python::InterpreterError::NotFound( interpreter_path, ))) => { - warn!( - "Ignoring existing virtual environment linked to non-existent Python interpreter: {}", - interpreter_path.user_display() - ); + if interpreter_path.is_symlink() { + let target_path = fs_err::read_link(&interpreter_path)?; + warn!( + "Ignoring existing virtual environment linked to non-existent Python interpreter: {} -> {}", + interpreter_path.user_display(), + target_path.user_display() + ); + } else { + warn!( + "Ignoring existing virtual environment with missing Python interpreter: {}", + interpreter_path.user_display() + ); + } + Ok(None) } Err(err) => Err(err.into()), diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index fb9c6a6e96c9..5324ac9e393e 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -401,10 +401,19 @@ impl FoundInterpreter { } } Err(uv_python::Error::Query(uv_python::InterpreterError::NotFound(path))) => { - warn_user!( - "Ignoring existing virtual environment linked to non-existent Python interpreter: {}", - path.user_display().cyan() - ); + if path.is_symlink() { + let target_path = fs_err::read_link(&path)?; + warn_user!( + "Ignoring existing virtual environment linked to non-existent Python interpreter: {} -> {}", + path.user_display().cyan(), + target_path.user_display().cyan(), + ); + } else { + warn_user!( + "Ignoring existing virtual environment with missing Python interpreter: {}", + path.user_display().cyan() + ); + } } Err(err) => return Err(err.into()), }; diff --git a/crates/uv/tests/sync.rs b/crates/uv/tests/sync.rs index 8cd6cdc7a33f..a8eb260de0a9 100644 --- a/crates/uv/tests/sync.rs +++ b/crates/uv/tests/sync.rs @@ -5,7 +5,7 @@ use assert_cmd::prelude::*; use assert_fs::{fixture::ChildPath, prelude::*}; use insta::assert_snapshot; -use common::{uv_snapshot, TestContext}; +use common::{uv_snapshot, venv_bin_path, TestContext}; use predicates::prelude::predicate; use tempfile::tempdir_in; @@ -2613,3 +2613,106 @@ fn sync_scripts_project_not_packaged() -> Result<()> { Ok(()) } + +#[test] +fn sync_invalid_environment() -> Result<()> { + let context = TestContext::new_with_versions(&["3.11", "3.12"]) + .with_filtered_virtualenv_bin() + .with_filtered_python_names(); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig"] + "#, + )?; + + // If the directory already exists and is not a virtual environment we should fail with an error + fs_err::create_dir(context.temp_dir.join(".venv"))?; + fs_err::write(context.temp_dir.join(".venv").join("file"), b"")?; + uv_snapshot!(context.filters(), context.sync(), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Project virtual environment directory `[VENV]/` cannot be used because it is not a virtual environment and is non-empty + "###); + + // But if it's just an incompatible virtual environment... + fs_err::remove_dir_all(context.temp_dir.join(".venv"))?; + uv_snapshot!(context.filters(), context.venv().arg("--python").arg("3.11"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.11.[X] interpreter at: [PYTHON-3.11] + Creating virtual environment at: .venv + Activate with: source .venv/[BIN]/activate + "###); + + // Even with some extraneous content... + fs_err::write(context.temp_dir.join(".venv").join("file"), b"")?; + + // We can delete and use it + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using Python 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + let bin = venv_bin_path(context.temp_dir.join(".venv")); + + // If it's there's a broken symlink, we should warn + #[cfg(unix)] + { + fs_err::remove_file(bin.join("python"))?; + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Ignoring existing virtual environment linked to non-existent Python interpreter: .venv/[BIN]/python -> python + Using Python 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + } + + // And if the Python executable is missing entirely we should warn + fs_err::remove_dir_all(&bin)?; + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: Ignoring existing virtual environment with missing Python interpreter: .venv/[BIN]/python + Using Python 3.12.[X] interpreter at: [PYTHON-3.12] + Removed virtual environment at: .venv + Creating virtual environment at: .venv + Resolved 2 packages in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "###); + + Ok(()) +}