mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-02 06:51:14 +00:00
Add uv toolchain find
(#4206)
Adds a command to find a toolchain on the system. Right now, it displays the path to the first matching toolchain. We'll probably have more rich output in the future (after implementing `toolchain show`). The eventual plan (separate from here) is to port all of the toolchain discovery tests to use this command. I'll add a few tests for this command here anyway.
This commit is contained in:
parent
b7fb0b445f
commit
accbb9b695
9 changed files with 258 additions and 2 deletions
|
@ -6,6 +6,7 @@ pub use crate::discovery::{
|
||||||
ToolchainSource, ToolchainSources, VersionRequest,
|
ToolchainSource, ToolchainSources, VersionRequest,
|
||||||
};
|
};
|
||||||
pub use crate::environment::PythonEnvironment;
|
pub use crate::environment::PythonEnvironment;
|
||||||
|
pub use crate::implementation::ImplementationName;
|
||||||
pub use crate::interpreter::Interpreter;
|
pub use crate::interpreter::Interpreter;
|
||||||
pub use crate::pointer_size::PointerSize;
|
pub use crate::pointer_size::PointerSize;
|
||||||
pub use crate::prefix::Prefix;
|
pub use crate::prefix::Prefix;
|
||||||
|
|
|
@ -1712,6 +1712,10 @@ pub(crate) enum ToolchainCommand {
|
||||||
|
|
||||||
/// Download and install a specific toolchain.
|
/// Download and install a specific toolchain.
|
||||||
Install(ToolchainInstallArgs),
|
Install(ToolchainInstallArgs),
|
||||||
|
|
||||||
|
/// Search for a toolchain
|
||||||
|
#[command(disable_version_flag = true)]
|
||||||
|
Find(ToolchainFindArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
@ -1743,6 +1747,13 @@ pub(crate) struct ToolchainInstallArgs {
|
||||||
pub(crate) force: bool,
|
pub(crate) force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
pub(crate) struct ToolchainFindArgs {
|
||||||
|
/// The toolchain request.
|
||||||
|
pub(crate) request: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
#[allow(clippy::struct_excessive_bools)]
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
pub(crate) struct IndexArgs {
|
pub(crate) struct IndexArgs {
|
||||||
|
|
|
@ -24,6 +24,7 @@ pub(crate) use project::sync::sync;
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
pub(crate) use self_update::self_update;
|
pub(crate) use self_update::self_update;
|
||||||
pub(crate) use tool::run::run as run_tool;
|
pub(crate) use tool::run::run as run_tool;
|
||||||
|
pub(crate) use toolchain::find::find as toolchain_find;
|
||||||
pub(crate) use toolchain::install::install as toolchain_install;
|
pub(crate) use toolchain::install::install as toolchain_install;
|
||||||
pub(crate) use toolchain::list::list as toolchain_list;
|
pub(crate) use toolchain::list::list as toolchain_list;
|
||||||
use uv_cache::Cache;
|
use uv_cache::Cache;
|
||||||
|
|
43
crates/uv/src/commands/toolchain/find.rs
Normal file
43
crates/uv/src/commands/toolchain/find.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use uv_cache::Cache;
|
||||||
|
use uv_configuration::PreviewMode;
|
||||||
|
use uv_fs::Simplified;
|
||||||
|
use uv_toolchain::{SystemPython, Toolchain, ToolchainRequest};
|
||||||
|
use uv_warnings::warn_user;
|
||||||
|
|
||||||
|
use crate::commands::ExitStatus;
|
||||||
|
use crate::printer::Printer;
|
||||||
|
|
||||||
|
/// Find a toolchain.
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) async fn find(
|
||||||
|
request: Option<String>,
|
||||||
|
preview: PreviewMode,
|
||||||
|
cache: &Cache,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<ExitStatus> {
|
||||||
|
if preview.is_disabled() {
|
||||||
|
warn_user!("`uv toolchain find` is experimental and may change without warning.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = match request {
|
||||||
|
Some(request) => ToolchainRequest::parse(&request),
|
||||||
|
None => ToolchainRequest::Any,
|
||||||
|
};
|
||||||
|
let toolchain = Toolchain::find_requested(
|
||||||
|
&request,
|
||||||
|
SystemPython::Required,
|
||||||
|
PreviewMode::Enabled,
|
||||||
|
cache,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
printer.stdout(),
|
||||||
|
"{}",
|
||||||
|
toolchain.interpreter().sys_executable().user_display()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(ExitStatus::Success)
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
|
pub(crate) mod find;
|
||||||
pub(crate) mod install;
|
pub(crate) mod install;
|
||||||
pub(crate) mod list;
|
pub(crate) mod list;
|
||||||
|
|
|
@ -797,6 +797,17 @@ async fn run() -> Result<ExitStatus> {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
Commands::Toolchain(ToolchainNamespace {
|
||||||
|
command: ToolchainCommand::Find(args),
|
||||||
|
}) => {
|
||||||
|
// Resolve the settings from the command-line arguments and workspace configuration.
|
||||||
|
let args = settings::ToolchainFindSettings::resolve(args, filesystem);
|
||||||
|
|
||||||
|
// Initialize the cache.
|
||||||
|
let cache = cache.init()?;
|
||||||
|
|
||||||
|
commands::toolchain_find(args.request, globals.preview, &cache, printer).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,8 @@ use crate::cli::{
|
||||||
AddArgs, BuildArgs, ColorChoice, GlobalArgs, IndexArgs, InstallerArgs, LockArgs, Maybe,
|
AddArgs, BuildArgs, ColorChoice, GlobalArgs, IndexArgs, InstallerArgs, LockArgs, Maybe,
|
||||||
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
|
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
|
||||||
PipSyncArgs, PipUninstallArgs, RefreshArgs, RemoveArgs, ResolverArgs, ResolverInstallerArgs,
|
PipSyncArgs, PipUninstallArgs, RefreshArgs, RemoveArgs, ResolverArgs, ResolverInstallerArgs,
|
||||||
RunArgs, SyncArgs, ToolRunArgs, ToolchainInstallArgs, ToolchainListArgs, VenvArgs,
|
RunArgs, SyncArgs, ToolRunArgs, ToolchainFindArgs, ToolchainInstallArgs, ToolchainListArgs,
|
||||||
|
VenvArgs,
|
||||||
};
|
};
|
||||||
use crate::commands::ListFormat;
|
use crate::commands::ListFormat;
|
||||||
|
|
||||||
|
@ -273,6 +274,23 @@ impl ToolchainInstallSettings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The resolved settings to use for a `toolchain find` invocation.
|
||||||
|
#[allow(clippy::struct_excessive_bools)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ToolchainFindSettings {
|
||||||
|
pub(crate) request: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolchainFindSettings {
|
||||||
|
/// Resolve the [`ToolchainFindSettings`] from the CLI and workspace configuration.
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
pub(crate) fn resolve(args: ToolchainFindArgs, _filesystem: Option<FilesystemOptions>) -> Self {
|
||||||
|
let ToolchainFindArgs { request } = args;
|
||||||
|
|
||||||
|
Self { request }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The resolved settings to use for a `sync` invocation.
|
/// The resolved settings to use for a `sync` invocation.
|
||||||
#[allow(clippy::struct_excessive_bools, dead_code)]
|
#[allow(clippy::struct_excessive_bools, dead_code)]
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
use assert_cmd::assert::{Assert, OutputAssertExt};
|
use assert_cmd::assert::{Assert, OutputAssertExt};
|
||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
use assert_fs::assert::PathAssert;
|
use assert_fs::assert::PathAssert;
|
||||||
use assert_fs::fixture::PathChild;
|
use assert_fs::fixture::{ChildPath, PathChild};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -287,6 +287,34 @@ impl TestContext {
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toolchains_dir(&self) -> ChildPath {
|
||||||
|
self.temp_dir.child("toolchains")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a `uv toolchain find` command with options shared across scenarios.
|
||||||
|
pub fn toolchain_find(&self) -> std::process::Command {
|
||||||
|
let mut command = std::process::Command::new(get_bin());
|
||||||
|
command
|
||||||
|
.arg("toolchain")
|
||||||
|
.arg("find")
|
||||||
|
.arg("--cache-dir")
|
||||||
|
.arg(self.cache_dir.path())
|
||||||
|
.env("VIRTUAL_ENV", self.venv.as_os_str())
|
||||||
|
.env("UV_NO_WRAP", "1")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", "/dev/null")
|
||||||
|
.env("UV_PREVIEW", "1")
|
||||||
|
.env("UV_TOOLCHAIN_DIR", self.toolchains_dir().as_os_str())
|
||||||
|
.current_dir(&self.temp_dir);
|
||||||
|
|
||||||
|
if cfg!(all(windows, debug_assertions)) {
|
||||||
|
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
|
||||||
|
// default windows stack of 1MB
|
||||||
|
command.env("UV_STACK_SIZE", (4 * 1024 * 1024).to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a `uv run` command with options shared across scenarios.
|
/// Create a `uv run` command with options shared across scenarios.
|
||||||
pub fn run(&self) -> std::process::Command {
|
pub fn run(&self) -> std::process::Command {
|
||||||
let mut command = self.run_without_exclude_newer();
|
let mut command = self.run_without_exclude_newer();
|
||||||
|
|
142
crates/uv/tests/toolchain_find.rs
Normal file
142
crates/uv/tests/toolchain_find.rs
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
#![cfg(all(feature = "python", feature = "pypi"))]
|
||||||
|
|
||||||
|
use common::{python_path_with_versions, uv_snapshot, TestContext};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn toolchain_find() {
|
||||||
|
let context: TestContext = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// No interpreters on the path
|
||||||
|
uv_snapshot!(context.filters(), context.toolchain_find(), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No Python interpreters found in provided path, active virtual environment, or search path
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let python_path = python_path_with_versions(&context.temp_dir, &["3.11", "3.12"])
|
||||||
|
.expect("Failed to create Python test path");
|
||||||
|
|
||||||
|
// Create some filters for the test interpreters, otherwise they'll be a path on the dev's machine
|
||||||
|
// TODO(zanieb): Standardize this when writing more tests
|
||||||
|
let python_path_filters = std::env::split_paths(&python_path)
|
||||||
|
.zip(["3.11", "3.12"])
|
||||||
|
.flat_map(|(path, version)| {
|
||||||
|
TestContext::path_patterns(path)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |pattern| {
|
||||||
|
(
|
||||||
|
format!("{pattern}python.*"),
|
||||||
|
format!("[PYTHON-PATH-{version}]"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let filters = python_path_filters
|
||||||
|
.iter()
|
||||||
|
.map(|(pattern, replacement)| (pattern.as_str(), replacement.as_str()))
|
||||||
|
.chain(context.filters())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// We find the first interpreter on the path
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.11]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Request Python 3.12
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.arg("3.12")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.12]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Request Python 3.11
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.arg("3.11")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.11]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Request CPython
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.arg("cpython")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.11]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Request CPython 3.12
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.arg("cpython@3.12")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.12]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Request PyPy
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.arg("pypy")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 2
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
error: No interpreter found for PyPy in provided path, active virtual environment, or search path
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Swap the order (but don't change the filters to preserve our indices)
|
||||||
|
let python_path = python_path_with_versions(&context.temp_dir, &["3.12", "3.11"])
|
||||||
|
.expect("Failed to create Python test path");
|
||||||
|
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.12]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Request Python 3.11
|
||||||
|
uv_snapshot!(filters, context.toolchain_find()
|
||||||
|
.arg("3.11")
|
||||||
|
.env("UV_TEST_PYTHON_PATH", &python_path), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
[PYTHON-PATH-3.11]
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
"###);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue