Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add uv toolchain find #4206

Merged
merged 2 commits into from
Jun 14, 2024
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
1 change: 1 addition & 0 deletions crates/uv-toolchain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use crate::discovery::{
ToolchainSource, ToolchainSources, VersionRequest,
};
pub use crate::environment::PythonEnvironment;
pub use crate::implementation::ImplementationName;
pub use crate::interpreter::Interpreter;
pub use crate::pointer_size::PointerSize;
pub use crate::prefix::Prefix;
Expand Down
11 changes: 11 additions & 0 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,10 @@ pub(crate) enum ToolchainCommand {

/// Download and install a specific toolchain.
Install(ToolchainInstallArgs),

/// Search for a toolchain
#[command(disable_version_flag = true)]
Find(ToolchainFindArgs),
}

#[derive(Args)]
Expand Down Expand Up @@ -1743,6 +1747,13 @@ pub(crate) struct ToolchainInstallArgs {
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)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct IndexArgs {
Expand Down
1 change: 1 addition & 0 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub(crate) use project::sync::sync;
#[cfg(feature = "self-update")]
pub(crate) use self_update::self_update;
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::list::list as toolchain_list;
use uv_cache::Cache;
Expand Down
43 changes: 43 additions & 0 deletions crates/uv/src/commands/toolchain/find.rs
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions crates/uv/src/commands/toolchain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub(crate) mod find;
pub(crate) mod install;
pub(crate) mod list;
11 changes: 11 additions & 0 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,17 @@ async fn run() -> Result<ExitStatus> {
)
.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
}
}
}

Expand Down
20 changes: 19 additions & 1 deletion crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use crate::cli::{
AddArgs, BuildArgs, ColorChoice, GlobalArgs, IndexArgs, InstallerArgs, LockArgs, Maybe,
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
PipSyncArgs, PipUninstallArgs, RefreshArgs, RemoveArgs, ResolverArgs, ResolverInstallerArgs,
RunArgs, SyncArgs, ToolRunArgs, ToolchainInstallArgs, ToolchainListArgs, VenvArgs,
RunArgs, SyncArgs, ToolRunArgs, ToolchainFindArgs, ToolchainInstallArgs, ToolchainListArgs,
VenvArgs,
};
use crate::commands::ListFormat;

Expand Down Expand Up @@ -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.
#[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)]
Expand Down
30 changes: 29 additions & 1 deletion crates/uv/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
use assert_cmd::assert::{Assert, OutputAssertExt};
use assert_cmd::Command;
use assert_fs::assert::PathAssert;
use assert_fs::fixture::PathChild;
use assert_fs::fixture::{ChildPath, PathChild};
use regex::Regex;
use std::borrow::BorrowMut;
use std::env;
Expand Down Expand Up @@ -287,6 +287,34 @@ impl TestContext {
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.
pub fn run(&self) -> std::process::Command {
let mut command = self.run_without_exclude_newer();
Expand Down
142 changes: 142 additions & 0 deletions crates/uv/tests/toolchain_find.rs
Original file line number Diff line number Diff line change
@@ -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