Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/uv-python/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,8 +588,8 @@ fn python_executables_from_search_path<'a>(
version: &'a VersionRequest,
implementation: Option<&'a ImplementationName>,
) -> impl Iterator<Item = PathBuf> + 'a {
// `UV_TEST_PYTHON_PATH` can be used to override `PATH` to limit Python executable availability in the test suite
let search_path = env::var_os(EnvVars::UV_TEST_PYTHON_PATH)
// `UV_PYTHON_SEARCH_PATH` can be used to override `PATH` for Python executable discovery
let search_path = env::var_os(EnvVars::UV_PYTHON_SEARCH_PATH)
.unwrap_or(env::var_os(EnvVars::PATH).unwrap_or_default());

let possible_names: Vec<_> = version
Expand Down
7 changes: 2 additions & 5 deletions crates/uv-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,9 @@ mod tests {

let mut run_vars = vec![
// Ensure `PATH` is used
(EnvVars::UV_TEST_PYTHON_PATH, None),
(EnvVars::UV_PYTHON_SEARCH_PATH, None),
// Keep discovery hermetic by disabling registry-based sources unless a test opts in.
(
EnvVars::UV_PYTHON_NO_REGISTRY,
Some(std::ffi::OsStr::new("1")),
),
(EnvVars::UV_PYTHON_NO_REGISTRY, Some(OsStr::new("1"))),
// Ignore active virtual environments (i.e. that the dev is using)
(EnvVars::VIRTUAL_ENV, None),
(EnvVars::PATH, path.as_deref()),
Expand Down
10 changes: 6 additions & 4 deletions crates/uv-static/src/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,12 @@ impl EnvVars {
#[attr_added_in("0.5.21")]
pub const UV_VENV_SEED: &'static str = "UV_VENV_SEED";

/// Used to override `PATH` to limit Python executable availability in the test suite.
#[attr_hidden]
#[attr_added_in("0.0.5")]
pub const UV_TEST_PYTHON_PATH: &'static str = "UV_TEST_PYTHON_PATH";
/// Used to override `PATH` for Python executable discovery.
///
/// When set, uv will search for Python interpreters in the directories specified by this
/// variable instead of `PATH`.
#[attr_added_in("next release")]
pub const UV_PYTHON_SEARCH_PATH: &'static str = "UV_PYTHON_SEARCH_PATH";

/// Include resolver and installer output related to environment modifications.
#[attr_hidden]
Expand Down
8 changes: 4 additions & 4 deletions crates/uv-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1183,7 +1183,7 @@ impl TestContext {
/// but snapshotted to a string.
/// * Use a fake `HOME` to avoid accidentally changing the developer's machine.
/// * Hide other Pythons with `UV_PYTHON_INSTALL_DIR` and installed interpreters with
/// `UV_TEST_PYTHON_PATH` and an active venv (if applicable) by removing `VIRTUAL_ENV`.
/// `UV_PYTHON_SEARCH_PATH` and an active venv (if applicable) by removing `VIRTUAL_ENV`.
/// * Increase the stack size to avoid stack overflows on windows due to large async functions.
pub fn add_shared_options(&self, command: &mut Command, activate_venv: bool) {
self.add_shared_args(command);
Expand Down Expand Up @@ -1232,7 +1232,7 @@ impl TestContext {
.env(EnvVars::UV_PYTHON_INSTALL_DIR, "")
// Installations are not allowed by default; see `Self::with_managed_python_dirs`
.env(EnvVars::UV_PYTHON_DOWNLOADS, "never")
.env(EnvVars::UV_TEST_PYTHON_PATH, self.python_path())
.env(EnvVars::UV_PYTHON_SEARCH_PATH, self.python_path())
.env(EnvVars::UV_EXCLUDE_NEWER, TEST_TIMESTAMP)
.env(EnvVars::UV_TEST_CURRENT_TIMESTAMP, TEST_TIMESTAMP)
.env(EnvVars::UV_TEST_AVAILABLE_VERSION_CUTOFF, TEST_TIMESTAMP)
Expand Down Expand Up @@ -2068,7 +2068,7 @@ pub fn create_venv_from_executable<P: AsRef<Path>>(

/// Create a `PATH` with the requested Python versions available in order.
///
/// Generally this should be used with `UV_TEST_PYTHON_PATH`.
/// Generally this should be used with `UV_PYTHON_SEARCH_PATH`.
pub fn python_path_with_versions(
temp_dir: &ChildPath,
python_versions: &[&str],
Expand All @@ -2083,7 +2083,7 @@ pub fn python_path_with_versions(

/// Returns a list of Python executables for the given versions.
///
/// Generally this should be used with `UV_TEST_PYTHON_PATH`.
/// Generally this should be used with `UV_PYTHON_SEARCH_PATH`.
pub fn python_installations_for_versions(
temp_dir: &ChildPath,
python_versions: &[&str],
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/tests/it/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,7 @@ fn compile_fallback_interpreter_broken_in_path() -> Result<()> {
.arg("--python-version")
.arg("3.12")
// In tests, we ignore `PATH` during Python discovery so we need to add the context `bin`
.env(EnvVars::UV_TEST_PYTHON_PATH, context.bin_dir.as_os_str()), @"
.env(EnvVars::UV_PYTHON_SEARCH_PATH, context.bin_dir.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/tests/it/pip_compile_scenarios.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
.arg(build_vendor_links_url());
context.add_shared_options(&mut command, true);
command.env_remove(EnvVars::UV_EXCLUDE_NEWER);
command.env(EnvVars::UV_TEST_PYTHON_PATH, python_path);
command.env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path);

command
}
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/tests/it/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9043,7 +9043,7 @@ fn install_incompatible_python_version_interpreter_broken_in_path() -> Result<()
.arg("-p").arg("3.12")
.arg("anyio")
// In tests, we ignore `PATH` during Python discovery so we need to add the context `bin`
.env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @"
.env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @"
success: false
exit_code: 2
----- stdout -----
Expand All @@ -9070,7 +9070,7 @@ fn install_incompatible_python_version_interpreter_broken_in_path() -> Result<()
.arg("-p").arg("3.12")
.arg("anyio")
// In tests, we ignore `PATH` during Python discovery so we need to add the context `bin`
.env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @"
.env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @"
success: false
exit_code: 2
----- stdout -----
Expand Down
75 changes: 67 additions & 8 deletions crates/uv/tests/it/python_find.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fn python_find() {
uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources();

// No interpreters on the path
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, ""), @"
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @"
success: false
exit_code: 2
----- stdout -----
Expand Down Expand Up @@ -724,7 +724,7 @@ fn python_find_venv() {

// Or at the front of the PATH
#[cfg(not(windows))]
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, child_dir.join(".venv").join("bin").as_os_str()), @"
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, child_dir.join(".venv").join("bin").as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -743,7 +743,7 @@ fn python_find_venv() {
])
.unwrap();

uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @"
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -762,7 +762,7 @@ fn python_find_venv() {
)
.unwrap();

uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, path.as_os_str()), @"
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, path.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
Expand Down Expand Up @@ -994,7 +994,7 @@ fn python_required_python_major_minor() {
.unwrap();

// Find `python3.11`, which is `>=3.11.4`.
uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @"
uv_snapshot!(context.filters(), context.python_find().arg(">=3.11.4, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -1004,7 +1004,7 @@ fn python_required_python_major_minor() {
");

// Find `python3.11`, which is `>3.11.4`.
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @"
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.4, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -1014,7 +1014,7 @@ fn python_required_python_major_minor() {
");

// Fail to find any matching Python interpreter.
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.255, <3.12").env(EnvVars::UV_TEST_PYTHON_PATH, context.temp_dir.child("child").path()), @"
uv_snapshot!(context.filters(), context.python_find().arg(">3.11.255, <3.12").env(EnvVars::UV_PYTHON_SEARCH_PATH, context.temp_dir.child("child").path()), @"
success: false
exit_code: 2
----- stdout -----
Expand Down Expand Up @@ -1167,7 +1167,7 @@ fn python_find_show_version() {
uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources();

// No interpreters found
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_TEST_PYTHON_PATH, "").arg("--show-version"), @"
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, "").arg("--show-version"), @"
success: false
exit_code: 2
----- stdout -----
Expand Down Expand Up @@ -1536,3 +1536,62 @@ fn python_find_equal() {
----- stderr -----
"###);
}

/// Test that `UV_PYTHON_SEARCH_PATH` overrides `PATH` for Python discovery.
#[test]
fn python_find_search_path() {
let context =
uv_test::test_context_with_versions!(&["3.11", "3.12"]).with_filtered_python_sources();

// When `UV_PYTHON_SEARCH_PATH` is empty, no interpreters are found
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
error: No interpreter found in [PYTHON SOURCES]
");

// When `UV_PYTHON_SEARCH_PATH` is set, it is used instead of `PATH`
uv_snapshot!(context.filters(), context.python_find(), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.11]

----- stderr -----
");

// We can use `UV_PYTHON_SEARCH_PATH` to control which Python versions are visible
let python_path_3_12_only = std::env::join_paths(
std::env::split_paths(&context.python_path())
.filter(|p| p.to_string_lossy().contains("3.12")),
)
.unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path_3_12_only.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]

----- stderr -----
");

// We can use `UV_PYTHON_SEARCH_PATH` to control the order of Python versions
let reversed_path = std::env::join_paths(
std::env::split_paths(&context.python_path())
.collect::<Vec<_>>()
.into_iter()
.rev(),
)
.unwrap();
uv_snapshot!(context.filters(), context.python_find().env(EnvVars::UV_PYTHON_SEARCH_PATH, reversed_path.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
[PYTHON-3.12]

----- stderr -----
");
}
10 changes: 5 additions & 5 deletions crates/uv/tests/it/python_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ fn python_install_automatic() {
uv_snapshot!(context.filters(), context.run()
.env_remove(EnvVars::VIRTUAL_ENV)
// In tests, we ignore `PATH` during Python discovery so we need to add the context `bin`
.env(EnvVars::UV_TEST_PYTHON_PATH, context.bin_dir.as_os_str())
.env(EnvVars::UV_PYTHON_SEARCH_PATH, context.bin_dir.as_os_str())
.arg("-p").arg("3.11")
.arg("python").arg("-c").arg("import sys; print(sys.version_info[:2])"), @"
success: true
Expand Down Expand Up @@ -1269,7 +1269,7 @@ fn python_install_freethreaded_and_gil_list() {

// List installed versions with registry discovery enabled.
// We remove `UV_PYTHON_NO_REGISTRY` to opt back into registry discovery, and remove
// `UV_TEST_PYTHON_PATH` so the test can discover the installed bin trampolines.
// `UV_PYTHON_SEARCH_PATH` so the test can discover the installed bin trampolines.
//
// Both the GIL and freethreaded variants should show entries from:
// - The registry (patch-versioned managed directory path)
Expand All @@ -1280,7 +1280,7 @@ fn python_install_freethreaded_and_gil_list() {
.arg("--only-installed")
.arg("--managed-python")
.env_remove(EnvVars::UV_PYTHON_NO_REGISTRY)
.env_remove(EnvVars::UV_TEST_PYTHON_PATH)
.env_remove(EnvVars::UV_PYTHON_SEARCH_PATH)
.env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @"
success: true
exit_code: 0
Expand All @@ -1297,7 +1297,7 @@ fn python_install_freethreaded_and_gil_list() {
.arg("--only-installed")
.arg("--managed-python")
.env_remove(EnvVars::UV_PYTHON_NO_REGISTRY)
.env_remove(EnvVars::UV_TEST_PYTHON_PATH)
.env_remove(EnvVars::UV_PYTHON_SEARCH_PATH)
.env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @"
success: true
exit_code: 0
Expand Down Expand Up @@ -1349,7 +1349,7 @@ fn python_install_registry_takes_precedence_over_no_registry() {
.arg("--only-installed")
.arg("--managed-python")
.env_remove(EnvVars::UV_PYTHON_NO_REGISTRY)
.env(EnvVars::UV_TEST_PYTHON_PATH, "")
.env(EnvVars::UV_PYTHON_SEARCH_PATH, "")
.env(EnvVars::UV_PYTHON_INSTALL_REGISTRY, "1"), @"
success: true
exit_code: 0
Expand Down
12 changes: 6 additions & 6 deletions crates/uv/tests/it/python_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn python_list() {
.with_filtered_python_keys()
.with_collapsed_whitespace();

uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, ""), @"
uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, ""), @"
success: true
exit_code: 0
----- stdout -----
Expand Down Expand Up @@ -303,7 +303,7 @@ fn python_list_duplicate_path_entries() {
)
.unwrap();

uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, &path), @"
uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, &path), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -325,7 +325,7 @@ fn python_list_duplicate_path_entries() {
))
.unwrap();

uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, &path), @"
uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, &path), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -346,7 +346,7 @@ fn python_list_duplicate_path_entries() {
)
.unwrap();

uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_TEST_PYTHON_PATH, &path), @"
uv_snapshot!(context.filters(), context.python_list().env(EnvVars::UV_PYTHON_SEARCH_PATH, &path), @"
success: true
exit_code: 0
----- stdout -----
Expand Down Expand Up @@ -527,7 +527,7 @@ fn python_list_managed_symlinks() {
.arg("3.10")
.arg("--only-installed")
.arg("--no-managed-python")
.env(EnvVars::UV_TEST_PYTHON_PATH, &bin_dir), @"
.env(EnvVars::UV_PYTHON_SEARCH_PATH, &bin_dir), @"
success: true
exit_code: 0
----- stdout -----
Expand All @@ -540,7 +540,7 @@ fn python_list_managed_symlinks() {
.arg("3.10")
.arg("--only-installed")
.arg("--managed-python")
.env(EnvVars::UV_TEST_PYTHON_PATH, &bin_dir), @"
.env(EnvVars::UV_PYTHON_SEARCH_PATH, &bin_dir), @"
success: true
exit_code: 0
----- stdout -----
Expand Down
6 changes: 3 additions & 3 deletions crates/uv/tests/it/venv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,7 @@ fn create_venv_unknown_python_minor() {
.arg("--python")
.arg("3.100")
// Unset this variable to force what the user would see
.env_remove(EnvVars::UV_TEST_PYTHON_PATH);
.env_remove(EnvVars::UV_PYTHON_SEARCH_PATH);

uv_snapshot!(context.filters(), &mut command, @"
success: false
Expand All @@ -949,7 +949,7 @@ fn create_venv_unknown_python_patch() {
.arg("--python")
.arg("3.12.100")
// Unset this variable to force what the user would see
.env_remove(EnvVars::UV_TEST_PYTHON_PATH);
.env_remove(EnvVars::UV_PYTHON_SEARCH_PATH);

uv_snapshot!(context.filters(), &mut command, @"
success: false
Expand Down Expand Up @@ -1211,7 +1211,7 @@ fn windows_shims() -> Result<()> {
// Create a virtual environment at `.venv` with the shim
uv_snapshot!(context.filters(), context.venv()
.arg(context.venv.as_os_str())
.env(EnvVars::UV_TEST_PYTHON_PATH, format!("{};{}", shim_path.display(), context.python_path().to_string_lossy())), @r###"
.env(EnvVars::UV_PYTHON_SEARCH_PATH, format!("{};{}", shim_path.display(), context.python_path().to_string_lossy())), @r###"
success: true
exit_code: 0
----- stdout -----
Expand Down
2 changes: 1 addition & 1 deletion scripts/scenarios/templates/compile.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn command(context: &TestContext, python_versions: &[&str]) -> Command {
.arg(build_vendor_links_url());
context.add_shared_options(&mut command, true);
command.env_remove(EnvVars::UV_EXCLUDE_NEWER);
command.env(EnvVars::UV_TEST_PYTHON_PATH, python_path);
command.env(EnvVars::UV_PYTHON_SEARCH_PATH, python_path);

command
}
Expand Down
Loading