mirror of https://github.com/astral-sh/uv
Add warning when `VIRTUAL_ENV` is set but will not be respected in project commands (#6864)
Following https://github.com/astral-sh/uv/pull/6834
This commit is contained in:
parent
88ba1e90a0
commit
2a425152b9
|
|
@ -5250,6 +5250,7 @@ dependencies = [
|
||||||
"pypi-types",
|
"pypi-types",
|
||||||
"regex",
|
"regex",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
|
"same-file",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ either = { workspace = true }
|
||||||
fs-err = { workspace = true }
|
fs-err = { workspace = true }
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
rustc-hash = { workspace = true }
|
rustc-hash = { workspace = true }
|
||||||
|
same-file = { workspace = true }
|
||||||
schemars = { workspace = true, optional = true }
|
schemars = { workspace = true, optional = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl};
|
use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl};
|
||||||
use pypi_types::{Requirement, RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
use pypi_types::{Requirement, RequirementSource, SupportedEnvironments, VerbatimParsedUrl};
|
||||||
use uv_fs::Simplified;
|
use uv_fs::{Simplified, CWD};
|
||||||
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
|
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::{warn_user, warn_user_once};
|
||||||
|
|
||||||
use crate::pyproject::{Project, PyProjectToml, Source, ToolUvWorkspace};
|
use crate::pyproject::{Project, PyProjectToml, Source, ToolUvWorkspace};
|
||||||
|
|
||||||
|
|
@ -442,7 +442,7 @@ impl Workspace {
|
||||||
/// it is resolved relative to the install path.
|
/// it is resolved relative to the install path.
|
||||||
pub fn venv(&self) -> PathBuf {
|
pub fn venv(&self) -> PathBuf {
|
||||||
/// Resolve the `UV_PROJECT_ENVIRONMENT` value, if any.
|
/// Resolve the `UV_PROJECT_ENVIRONMENT` value, if any.
|
||||||
fn from_environment_variable(workspace: &Workspace) -> Option<PathBuf> {
|
fn from_project_environment_variable(workspace: &Workspace) -> Option<PathBuf> {
|
||||||
let value = std::env::var_os("UV_PROJECT_ENVIRONMENT")?;
|
let value = std::env::var_os("UV_PROJECT_ENVIRONMENT")?;
|
||||||
|
|
||||||
if value.is_empty() {
|
if value.is_empty() {
|
||||||
|
|
@ -458,8 +458,65 @@ impl Workspace {
|
||||||
Some(workspace.install_path.join(path))
|
Some(workspace.install_path.join(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(zanieb): Warn if `VIRTUAL_ENV` is set and does not match
|
// Resolve the `VIRTUAL_ENV` variable, if any.
|
||||||
from_environment_variable(self).unwrap_or_else(|| self.install_path.join(".venv"))
|
fn from_virtual_env_variable() -> Option<PathBuf> {
|
||||||
|
let value = std::env::var_os("VIRTUAL_ENV")?;
|
||||||
|
|
||||||
|
if value.is_empty() {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = PathBuf::from(value);
|
||||||
|
if path.is_absolute() {
|
||||||
|
return Some(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resolve the path relative to current directory.
|
||||||
|
// Note this differs from `UV_PROJECT_ENVIRONMENT`
|
||||||
|
Some(CWD.join(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to check if the two paths refer to the same directory.
|
||||||
|
fn is_same_dir(left: &Path, right: &Path) -> Option<bool> {
|
||||||
|
// First, attempt to check directly
|
||||||
|
if let Ok(value) = same_file::is_same_file(left, right) {
|
||||||
|
return Some(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Often, one of the directories won't exist yet so perform the comparison up a level
|
||||||
|
if let (Some(left_parent), Some(right_parent), Some(left_name), Some(right_name)) = (
|
||||||
|
left.parent(),
|
||||||
|
right.parent(),
|
||||||
|
left.file_name(),
|
||||||
|
right.file_name(),
|
||||||
|
) {
|
||||||
|
match same_file::is_same_file(left_parent, right_parent) {
|
||||||
|
Ok(true) => return Some(left_name == right_name),
|
||||||
|
Ok(false) => return Some(false),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// We couldn't determine if they're the same
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the default value
|
||||||
|
let project_env = from_project_environment_variable(self)
|
||||||
|
.unwrap_or_else(|| self.install_path.join(".venv"));
|
||||||
|
|
||||||
|
// Warn if it conflicts with `VIRTUAL_ENV`
|
||||||
|
if let Some(from_virtual_env) = from_virtual_env_variable() {
|
||||||
|
if !is_same_dir(&from_virtual_env, &project_env).unwrap_or(false) {
|
||||||
|
warn_user_once!(
|
||||||
|
"`VIRTUAL_ENV={}` does not match the project environment path `{}` and will be ignored",
|
||||||
|
from_virtual_env.user_display(),
|
||||||
|
project_env.user_display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
project_env
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The members of the workspace.
|
/// The members of the workspace.
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ fn init_application() -> Result<()> {
|
||||||
Hello from foo!
|
Hello from foo!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
|
@ -306,6 +307,7 @@ fn init_application_package() -> Result<()> {
|
||||||
Hello from foo!
|
Hello from foo!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
|
@ -377,6 +379,7 @@ fn init_library() -> Result<()> {
|
||||||
Hello from foo!
|
Hello from foo!
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
|
|
||||||
|
|
@ -1307,6 +1307,7 @@ fn run_from_directory() -> Result<()> {
|
||||||
3.12.[X]
|
3.12.[X]
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 1 package in [TIME]
|
Resolved 1 package in [TIME]
|
||||||
|
|
|
||||||
|
|
@ -1747,3 +1747,147 @@ fn sync_workspace_custom_environment_path() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test for warnings when `VIRTUAL_ENV` is set but will not be respected.
|
||||||
|
#[test]
|
||||||
|
fn sync_virtual_env_warning() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
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"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// We should not warn if it matches the project environment
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", context.temp_dir.join(".venv")), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Including if it's a relative path that matches
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", ".venv"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Or, if it's a link that resolves to the same path
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
use fs_err::os::unix::fs::symlink;
|
||||||
|
|
||||||
|
let link = context.temp_dir.join("link");
|
||||||
|
symlink(context.temp_dir.join(".venv"), &link)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", link), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
|
// But we should warn if it's a different path
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", "foo"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=foo` does not match the project environment path `.venv` and will be ignored
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Including absolute paths
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", context.temp_dir.join("foo")), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=foo` does not match the project environment path `.venv` and will be ignored
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// We should not warn if the project environment has been customized and matches
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", "foo").env("UV_PROJECT_ENVIRONMENT", "foo"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
|
Creating virtualenv at: foo
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// But we should warn if they don't match still
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", "foo").env("UV_PROJECT_ENVIRONMENT", "bar"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=foo` does not match the project environment path `bar` and will be ignored
|
||||||
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
|
Creating virtualenv at: bar
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ iniconfig==2.0.0
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let child = context.temp_dir.child("child");
|
||||||
|
child.create_dir_all()?;
|
||||||
|
|
||||||
|
// And `VIRTUAL_ENV` is resolved relative to the project root so with relative paths we should
|
||||||
|
// warn from a child too
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", "foo").env("UV_PROJECT_ENVIRONMENT", "foo").current_dir(&child), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=foo` does not match the project environment path `[TEMP_DIR]/foo` and will be ignored
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// But, a matching absolute path shouldn't warn
|
||||||
|
uv_snapshot!(context.filters(), context.sync().env("VIRTUAL_ENV", context.temp_dir.join("foo")).env("UV_PROJECT_ENVIRONMENT", "foo").current_dir(&child), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 2 packages in [TIME]
|
||||||
|
Audited 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -377,6 +377,7 @@ fn test_uv_run_with_package_virtual_workspace() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON]
|
Using Python 3.12.[X] interpreter at: [PYTHON]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
|
|
@ -402,6 +403,7 @@ fn test_uv_run_with_package_virtual_workspace() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
Prepared 2 packages in [TIME]
|
Prepared 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
|
|
@ -435,6 +437,7 @@ fn test_uv_run_virtual_workspace_root() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
|
|
@ -479,6 +482,7 @@ fn test_uv_run_with_package_root_workspace() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON]
|
Using Python 3.12.[X] interpreter at: [PYTHON]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
|
|
@ -504,6 +508,7 @@ fn test_uv_run_with_package_root_workspace() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
Prepared 2 packages in [TIME]
|
Prepared 2 packages in [TIME]
|
||||||
Installed 2 packages in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
|
|
@ -542,6 +547,7 @@ fn test_uv_run_isolate() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
|
||||||
Creating virtualenv at: .venv
|
Creating virtualenv at: .venv
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
|
|
@ -572,6 +578,7 @@ fn test_uv_run_isolate() -> Result<()> {
|
||||||
Success
|
Success
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
|
warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored
|
||||||
Resolved 8 packages in [TIME]
|
Resolved 8 packages in [TIME]
|
||||||
Audited 5 packages in [TIME]
|
Audited 5 packages in [TIME]
|
||||||
"###
|
"###
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue