Allow configuring the toolchain fetch strategy (#4601)

Adds a `toolchain-fetch` option alongside `toolchain-preference` with
`automatic` (default) and `manual` values allowing automatic toolchain
fetches to be disabled (replaces
https://github.com/astral-sh/uv/pull/4425). When `manual`, toolchains
must be installed with `uv toolchain install`.

Note this was previously implemented with `if-necessary`, `always`,
`never` variants but the interaction between this and
`toolchain-preference` was too confusing. By reducing to a binary
option, things should be clearer. The `if-necessary` behavior moved to
`toolchain-preference=installed`. See
https://github.com/astral-sh/uv/pull/4601#discussion_r1657839633 and
https://github.com/astral-sh/uv/pull/4601#discussion_r1658658755
This commit is contained in:
Zanie Blue 2024-07-01 21:54:24 -04:00 committed by GitHub
parent ec2723a9f5
commit 6799cc883a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 219 additions and 89 deletions

View file

@ -13,7 +13,7 @@ use uv_configuration::{
};
use uv_normalize::{ExtraName, PackageName};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_toolchain::{PythonVersion, ToolchainPreference};
use uv_toolchain::{PythonVersion, ToolchainFetch, ToolchainPreference};
pub mod compat;
pub mod options;
@ -118,10 +118,14 @@ pub struct GlobalArgs {
#[arg(global = true, long, overrides_with("offline"), hide = true)]
pub no_offline: bool,
/// Whether to use system or uv-managed Python toolchains.
/// Whether to prefer Python toolchains from uv or on the system.
#[arg(global = true, long)]
pub toolchain_preference: Option<ToolchainPreference>,
/// Whether to automatically download Python toolchains when required.
#[arg(global = true, long)]
pub toolchain_fetch: Option<ToolchainFetch>,
/// Whether to enable experimental, preview features.
#[arg(global = true, long, hide = true, env = "UV_PREVIEW", value_parser = clap::builder::BoolishValueParser::new(), overrides_with("no_preview"))]
pub preview: bool,

View file

@ -5,7 +5,7 @@ use distribution_types::IndexUrl;
use install_wheel_rs::linker::LinkMode;
use uv_configuration::{ConfigSettings, IndexStrategy, KeyringProviderType, TargetTriple};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_toolchain::{PythonVersion, ToolchainPreference};
use uv_toolchain::{PythonVersion, ToolchainFetch, ToolchainPreference};
use crate::{FilesystemOptions, PipOptions};
@ -70,6 +70,7 @@ impl_combine_or!(ResolutionMode);
impl_combine_or!(String);
impl_combine_or!(TargetTriple);
impl_combine_or!(ToolchainPreference);
impl_combine_or!(ToolchainFetch);
impl_combine_or!(bool);
impl<T> Combine for Option<Vec<T>> {

View file

@ -11,7 +11,7 @@ use uv_configuration::{
use uv_macros::CombineOptions;
use uv_normalize::{ExtraName, PackageName};
use uv_resolver::{AnnotationStyle, ExcludeNewer, PreReleaseMode, ResolutionMode};
use uv_toolchain::{PythonVersion, ToolchainPreference};
use uv_toolchain::{PythonVersion, ToolchainFetch, ToolchainPreference};
/// A `pyproject.toml` with an (optional) `[tool.uv]` section.
#[allow(dead_code)]
@ -60,6 +60,7 @@ pub struct GlobalOptions {
pub cache_dir: Option<PathBuf>,
pub preview: Option<bool>,
pub toolchain_preference: Option<ToolchainPreference>,
pub toolchain_fetch: Option<ToolchainFetch>,
}
/// Settings relevant to all installer operations.

View file

@ -57,20 +57,31 @@ pub enum ToolchainRequest {
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ToolchainPreference {
/// Only use managed interpreters, never use system interpreters.
/// Only use managed toolchains, never use system toolchains.
OnlyManaged,
/// Prefer installed managed interpreters, but use system interpreters if not found.
/// If neither can be found, download a managed interpreter.
/// Prefer installed toolchains, only download managed toolchains if no system toolchain is found.
#[default]
PreferInstalledManaged,
/// Prefer managed interpreters, even if one needs to be downloaded, but use system interpreters if found.
Installed,
/// Prefer managed toolchains over system toolchains, even if one needs to be downloaded.
PreferManaged,
/// Prefer system interpreters, only use managed interpreters if no system interpreter is found.
/// Prefer system toolchains, only use managed toolchains if no system toolchain is found.
PreferSystem,
/// Only use system interpreters, never use managed interpreters.
/// Only use system toolchains, never use managed toolchains.
OnlySystem,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum ToolchainFetch {
/// Automatically fetch managed toolchains when needed.
#[default]
Automatic,
/// Do not automatically fetch managed toolchains; require explicit installation.
Manual,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum EnvironmentPreference {
/// Only use virtual environments, never allow a system environment.
@ -302,12 +313,7 @@ fn python_executables_from_installed<'a>(
match preference {
ToolchainPreference::OnlyManaged => Box::new(from_managed_toolchains),
ToolchainPreference::PreferInstalledManaged => Box::new(
from_managed_toolchains
.chain(from_search_path)
.chain(from_py_launcher),
),
ToolchainPreference::PreferManaged => Box::new(
ToolchainPreference::PreferManaged | ToolchainPreference::Installed => Box::new(
from_managed_toolchains
.chain(from_search_path)
.chain(from_py_launcher),
@ -1147,9 +1153,7 @@ impl ToolchainPreference {
match self {
ToolchainPreference::OnlyManaged => matches!(source, ToolchainSource::Managed),
ToolchainPreference::PreferInstalledManaged
| Self::PreferManaged
| Self::PreferSystem => matches!(
Self::PreferManaged | Self::PreferSystem | Self::Installed => matches!(
source,
ToolchainSource::Managed
| ToolchainSource::SearchPath
@ -1179,11 +1183,17 @@ impl ToolchainPreference {
pub(crate) fn allows_managed(self) -> bool {
matches!(
self,
Self::PreferManaged | Self::PreferInstalledManaged | Self::OnlyManaged
Self::PreferManaged | Self::OnlyManaged | Self::Installed
)
}
}
impl ToolchainFetch {
pub fn is_automatic(self) -> bool {
matches!(self, Self::Automatic)
}
}
impl EnvironmentPreference {
pub fn from_system_flag(system: bool, mutable: bool) -> Self {
match (system, mutable) {
@ -1481,7 +1491,7 @@ impl fmt::Display for ToolchainPreference {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let s = match self {
Self::OnlyManaged => "managed toolchains",
Self::PreferManaged | Self::PreferInstalledManaged | Self::PreferSystem => {
Self::PreferManaged | Self::Installed | Self::PreferSystem => {
if cfg!(windows) {
"managed toolchains, system path, or `py` launcher"
} else {

View file

@ -125,32 +125,38 @@ impl PythonDownloadRequest {
self
}
/// Construct a new [`PythonDownloadRequest`] from a [`ToolchainRequest`] if possible.
///
/// Returns [`None`] if the request kind is not compatible with a download, e.g., it is
/// a request for a specific directory or executable name.
pub fn try_from_request(request: &ToolchainRequest) -> Option<Self> {
Self::from_request(request).ok()
}
/// Construct a new [`PythonDownloadRequest`] from a [`ToolchainRequest`].
pub fn from_request(request: ToolchainRequest) -> Result<Self, Error> {
let result = match request {
ToolchainRequest::Version(version) => Self::default().with_version(version),
pub fn from_request(request: &ToolchainRequest) -> Result<Self, Error> {
match request {
ToolchainRequest::Version(version) => Ok(Self::default().with_version(version.clone())),
ToolchainRequest::Implementation(implementation) => {
Self::default().with_implementation(implementation)
Ok(Self::default().with_implementation(*implementation))
}
ToolchainRequest::ImplementationVersion(implementation, version) => Self::default()
.with_implementation(implementation)
.with_version(version),
ToolchainRequest::Key(request) => request,
ToolchainRequest::Any => Self::default(),
ToolchainRequest::ImplementationVersion(implementation, version) => Ok(Self::default()
.with_implementation(*implementation)
.with_version(version.clone())),
ToolchainRequest::Key(request) => Ok(request.clone()),
ToolchainRequest::Any => Ok(Self::default()),
// We can't download a toolchain for these request kinds
ToolchainRequest::Directory(_)
| ToolchainRequest::ExecutableName(_)
| ToolchainRequest::File(_) => {
return Err(Error::InvalidRequestKind(request));
}
};
Ok(result)
| ToolchainRequest::File(_) => Err(Error::InvalidRequestKind(request.clone())),
}
}
/// Fill empty entries with default values.
///
/// Platform information is pulled from the environment.
pub fn fill(mut self) -> Result<Self, Error> {
#[must_use]
pub fn fill(mut self) -> Self {
if self.implementation.is_none() {
self.implementation = Some(ImplementationName::CPython);
}
@ -163,7 +169,7 @@ impl PythonDownloadRequest {
if self.libc.is_none() {
self.libc = Some(Libc::from_env());
}
Ok(self)
self
}
/// Construct a new [`PythonDownloadRequest`] with platform information from the environment.

View file

@ -2,7 +2,7 @@
use thiserror::Error;
pub use crate::discovery::{
find_toolchains, EnvironmentPreference, Error as DiscoveryError, SystemPython,
find_toolchains, EnvironmentPreference, Error as DiscoveryError, SystemPython, ToolchainFetch,
ToolchainNotFound, ToolchainPreference, ToolchainRequest, ToolchainSource, VersionRequest,
};
pub use crate::environment::PythonEnvironment;

View file

@ -14,7 +14,9 @@ use crate::downloads::{DownloadResult, PythonDownload, PythonDownloadRequest};
use crate::implementation::LenientImplementationName;
use crate::managed::{InstalledToolchain, InstalledToolchains};
use crate::platform::{Arch, Libc, Os};
use crate::{Error, Interpreter, PythonVersion, ToolchainPreference, ToolchainSource};
use crate::{
Error, Interpreter, PythonVersion, ToolchainFetch, ToolchainPreference, ToolchainSource,
};
/// A Python interpreter and accompanying tools.
#[derive(Clone, Debug)]
@ -79,18 +81,36 @@ impl Toolchain {
request: Option<ToolchainRequest>,
environments: EnvironmentPreference,
preference: ToolchainPreference,
client_builder: BaseClientBuilder<'a>,
toolchain_fetch: ToolchainFetch,
client_builder: &BaseClientBuilder<'a>,
cache: &Cache,
) -> Result<Self, Error> {
let request = request.unwrap_or_default();
// Perform a find first
// Perform a fetch aggressively if managed toolchains are preferred
if matches!(preference, ToolchainPreference::PreferManaged)
&& toolchain_fetch.is_automatic()
{
if let Some(request) = PythonDownloadRequest::try_from_request(&request) {
return Self::fetch(request, client_builder, cache).await;
}
}
// Search for the toolchain
match Self::find(&request, environments, preference, cache) {
Ok(venv) => Ok(venv),
Err(Error::MissingToolchain(_))
if preference.allows_managed() && client_builder.connectivity.is_online() =>
// If missing and allowed, perform a fetch
err @ Err(Error::MissingToolchain(_))
if preference.allows_managed()
&& toolchain_fetch.is_automatic()
&& client_builder.connectivity.is_online() =>
{
debug!("Requested Python not found, checking for available download...");
Self::fetch(request, client_builder, cache).await
if let Some(request) = PythonDownloadRequest::try_from_request(&request) {
debug!("Requested Python not found, checking for available download...");
Self::fetch(request, client_builder, cache).await
} else {
err
}
}
Err(err) => Err(err),
}
@ -98,14 +118,13 @@ impl Toolchain {
/// Download and install the requested toolchain.
pub async fn fetch<'a>(
request: ToolchainRequest,
client_builder: BaseClientBuilder<'a>,
request: PythonDownloadRequest,
client_builder: &BaseClientBuilder<'a>,
cache: &Cache,
) -> Result<Self, Error> {
let toolchains = InstalledToolchains::from_settings()?.init()?;
let toolchain_dir = toolchains.root();
let request = PythonDownloadRequest::from_request(request)?.fill()?;
let download = PythonDownload::from_request(&request)?;
let client = client_builder.build();

View file

@ -12,7 +12,7 @@ use uv_git::GitResolver;
use uv_normalize::PackageName;
use uv_requirements::{NamedRequirementsResolver, RequirementsSource, RequirementsSpecification};
use uv_resolver::{FlatIndex, InMemoryIndex};
use uv_toolchain::{ToolchainPreference, ToolchainRequest};
use uv_toolchain::{ToolchainFetch, ToolchainPreference, ToolchainRequest};
use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_warnings::warn_user_once;
@ -39,6 +39,7 @@ pub(crate) async fn add(
python: Option<String>,
settings: ResolverInstallerSettings,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
preview: PreviewMode,
connectivity: Connectivity,
concurrency: Concurrency,
@ -65,6 +66,7 @@ pub(crate) async fn add(
project.workspace(),
python.as_deref().map(ToolchainRequest::parse),
toolchain_preference,
toolchain_fetch,
connectivity,
native_tls,
cache,

View file

@ -11,7 +11,7 @@ use uv_distribution::{Workspace, DEV_DEPENDENCIES};
use uv_git::ResolvedRepositoryReference;
use uv_requirements::upgrade::{read_lockfile, LockedRequirements};
use uv_resolver::{FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython};
use uv_toolchain::{Interpreter, ToolchainPreference, ToolchainRequest};
use uv_toolchain::{Interpreter, ToolchainFetch, ToolchainPreference, ToolchainRequest};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
use uv_warnings::{warn_user, warn_user_once};
@ -26,6 +26,7 @@ pub(crate) async fn lock(
settings: ResolverSettings,
preview: PreviewMode,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
@ -44,6 +45,7 @@ pub(crate) async fn lock(
&workspace,
python.as_deref().map(ToolchainRequest::parse),
toolchain_preference,
toolchain_fetch,
connectivity,
native_tls,
cache,

View file

@ -19,7 +19,7 @@ use uv_requirements::{NamedRequirementsResolver, RequirementsSpecification};
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder, PythonRequirement, RequiresPython};
use uv_toolchain::{
request_from_version_file, EnvironmentPreference, Interpreter, PythonEnvironment, Toolchain,
ToolchainPreference, ToolchainRequest, VersionRequest,
ToolchainFetch, ToolchainPreference, ToolchainRequest, VersionRequest,
};
use uv_types::{BuildIsolation, HashStrategy, InFlight};
@ -127,6 +127,7 @@ impl FoundInterpreter {
workspace: &Workspace,
python_request: Option<ToolchainRequest>,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
connectivity: Connectivity,
native_tls: bool,
cache: &Cache,
@ -183,7 +184,8 @@ impl FoundInterpreter {
python_request,
EnvironmentPreference::OnlySystem,
toolchain_preference,
client_builder,
toolchain_fetch,
&client_builder,
cache,
)
.await?
@ -222,6 +224,7 @@ pub(crate) async fn get_or_init_environment(
workspace: &Workspace,
python: Option<ToolchainRequest>,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
connectivity: Connectivity,
native_tls: bool,
cache: &Cache,
@ -231,6 +234,7 @@ pub(crate) async fn get_or_init_environment(
workspace,
python,
toolchain_preference,
toolchain_fetch,
connectivity,
native_tls,
cache,

View file

@ -7,7 +7,7 @@ use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode};
use uv_distribution::pyproject::DependencyType;
use uv_distribution::pyproject_mut::PyProjectTomlMut;
use uv_distribution::{ProjectWorkspace, VirtualProject, Workspace};
use uv_toolchain::{ToolchainPreference, ToolchainRequest};
use uv_toolchain::{ToolchainFetch, ToolchainPreference, ToolchainRequest};
use uv_warnings::{warn_user, warn_user_once};
use crate::commands::pip::operations::Modifications;
@ -23,6 +23,7 @@ pub(crate) async fn remove(
package: Option<PackageName>,
python: Option<String>,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
preview: PreviewMode,
connectivity: Connectivity,
concurrency: Concurrency,
@ -86,6 +87,7 @@ pub(crate) async fn remove(
project.workspace(),
python.as_deref().map(ToolchainRequest::parse),
toolchain_preference,
toolchain_fetch,
connectivity,
native_tls,
cache,

View file

@ -16,7 +16,7 @@ use uv_normalize::PackageName;
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_toolchain::{
request_from_version_file, EnvironmentPreference, Interpreter, PythonEnvironment, Toolchain,
ToolchainPreference, ToolchainRequest, VersionRequest,
ToolchainFetch, ToolchainPreference, ToolchainRequest, VersionRequest,
};
use uv_warnings::warn_user_once;
@ -38,6 +38,7 @@ pub(crate) async fn run(
isolated: bool,
preview: PreviewMode,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
@ -89,7 +90,8 @@ pub(crate) async fn run(
python_request,
EnvironmentPreference::Any,
toolchain_preference,
client_builder,
toolchain_fetch,
&client_builder,
cache,
)
.await?
@ -170,6 +172,7 @@ pub(crate) async fn run(
project.workspace(),
python.as_deref().map(ToolchainRequest::parse),
toolchain_preference,
toolchain_fetch,
connectivity,
native_tls,
cache,
@ -222,7 +225,8 @@ pub(crate) async fn run(
// No opt-in is required for system environments, since we are not mutating it.
EnvironmentPreference::Any,
toolchain_preference,
client_builder,
toolchain_fetch,
&client_builder,
cache,
)
.await?;
@ -261,7 +265,8 @@ pub(crate) async fn run(
python.as_deref().map(ToolchainRequest::parse),
EnvironmentPreference::Any,
toolchain_preference,
client_builder,
toolchain_fetch,
&client_builder,
cache,
)
.await?

View file

@ -7,7 +7,7 @@ use uv_dispatch::BuildDispatch;
use uv_distribution::{VirtualProject, DEV_DEPENDENCIES};
use uv_installer::SitePackages;
use uv_resolver::{FlatIndex, Lock};
use uv_toolchain::{PythonEnvironment, ToolchainPreference, ToolchainRequest};
use uv_toolchain::{PythonEnvironment, ToolchainFetch, ToolchainPreference, ToolchainRequest};
use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_warnings::warn_user_once;
@ -24,6 +24,7 @@ pub(crate) async fn sync(
modifications: Modifications,
python: Option<String>,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
settings: InstallerSettings,
preview: PreviewMode,
connectivity: Connectivity,
@ -44,6 +45,7 @@ pub(crate) async fn sync(
project.workspace(),
python.as_deref().map(ToolchainRequest::parse),
toolchain_preference,
toolchain_fetch,
connectivity,
native_tls,
cache,

View file

@ -20,7 +20,8 @@ use uv_normalize::PackageName;
use uv_requirements::RequirementsSpecification;
use uv_tool::{entrypoint_paths, find_executable_directory, InstalledTools, Tool, ToolEntrypoint};
use uv_toolchain::{
EnvironmentPreference, Interpreter, Toolchain, ToolchainPreference, ToolchainRequest,
EnvironmentPreference, Interpreter, Toolchain, ToolchainFetch, ToolchainPreference,
ToolchainRequest,
};
use uv_warnings::warn_user_once;
@ -39,6 +40,7 @@ pub(crate) async fn install(
settings: ResolverInstallerSettings,
preview: PreviewMode,
toolchain_preference: ToolchainPreference,
_toolchain_fetch: ToolchainFetch,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
@ -49,6 +51,7 @@ pub(crate) async fn install(
warn_user_once!("`uv tool install` is experimental and may change without warning.");
}
// TODO(zanieb): Use `find_or_fetch` here
let interpreter = Toolchain::find(
&python
.as_deref()

View file

@ -16,7 +16,8 @@ use uv_configuration::{Concurrency, PreviewMode};
use uv_normalize::PackageName;
use uv_requirements::{RequirementsSource, RequirementsSpecification};
use uv_toolchain::{
EnvironmentPreference, PythonEnvironment, Toolchain, ToolchainPreference, ToolchainRequest,
EnvironmentPreference, PythonEnvironment, Toolchain, ToolchainFetch, ToolchainPreference,
ToolchainRequest,
};
use uv_warnings::warn_user_once;
@ -35,6 +36,7 @@ pub(crate) async fn run(
_isolated: bool,
preview: PreviewMode,
toolchain_preference: ToolchainPreference,
_toolchain_fetch: ToolchainFetch,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
@ -76,6 +78,7 @@ pub(crate) async fn run(
// Discover an interpreter.
// Note we force preview on during `uv tool run` for now since the entire interface is in preview
// TODO(zanieb): We should use `find_or_fetch` here
let interpreter = Toolchain::find(
&python
.as_deref()

View file

@ -1,6 +1,5 @@
use anyhow::Result;
use futures::StreamExt;
use itertools::Itertools;
use std::fmt::Write;
use uv_cache::Cache;
use uv_client::Connectivity;
@ -48,7 +47,7 @@ pub(crate) async fn install(
let download_requests = requests
.iter()
.map(|request| PythonDownloadRequest::from_request(request.clone()))
.map(PythonDownloadRequest::from_request)
.collect::<Result<Vec<_>, downloads::Error>>()?;
let installed_toolchains: Vec<_> = toolchains.find_all()?.collect();
@ -105,8 +104,7 @@ pub(crate) async fn install(
.into_iter()
// Populate the download requests with defaults
.map(PythonDownloadRequest::fill)
.map(|request| request.map(|inner| PythonDownload::from_request(&inner)))
.flatten_ok()
.map(|request| PythonDownload::from_request(&request))
.collect::<Result<Vec<_>, uv_toolchain::downloads::Error>>()?;
// Construct a client

View file

@ -8,8 +8,8 @@ use uv_configuration::PreviewMode;
use uv_fs::Simplified;
use uv_toolchain::downloads::PythonDownloadRequest;
use uv_toolchain::{
find_toolchains, DiscoveryError, EnvironmentPreference, Toolchain, ToolchainNotFound,
ToolchainPreference, ToolchainRequest, ToolchainSource,
find_toolchains, DiscoveryError, EnvironmentPreference, Toolchain, ToolchainFetch,
ToolchainNotFound, ToolchainPreference, ToolchainRequest, ToolchainSource,
};
use uv_warnings::warn_user_once;
@ -25,11 +25,13 @@ enum Kind {
}
/// List available toolchains.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn list(
kinds: ToolchainListKinds,
all_versions: bool,
all_platforms: bool,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
preview: PreviewMode,
cache: &Cache,
printer: Printer,
@ -40,11 +42,18 @@ pub(crate) async fn list(
let download_request = match kinds {
ToolchainListKinds::Installed => None,
ToolchainListKinds::Default => Some(if all_platforms {
PythonDownloadRequest::default()
} else {
PythonDownloadRequest::from_env()?
}),
ToolchainListKinds::Default => {
if toolchain_fetch.is_automatic() {
Some(if all_platforms {
PythonDownloadRequest::default()
} else {
PythonDownloadRequest::from_env()?
})
} else {
// If fetching is not automatic, then don't show downloads as available by default
None
}
}
};
let downloads = download_request

View file

@ -24,8 +24,8 @@ use uv_fs::Simplified;
use uv_git::GitResolver;
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex};
use uv_toolchain::{
request_from_version_file, EnvironmentPreference, Toolchain, ToolchainPreference,
ToolchainRequest,
request_from_version_file, EnvironmentPreference, Toolchain, ToolchainFetch,
ToolchainPreference, ToolchainRequest,
};
use uv_types::{BuildContext, BuildIsolation, HashStrategy, InFlight};
@ -43,6 +43,7 @@ pub(crate) async fn venv(
path: &Path,
python_request: Option<&str>,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
link_mode: LinkMode,
index_locations: &IndexLocations,
index_strategy: IndexStrategy,
@ -71,6 +72,7 @@ pub(crate) async fn venv(
seed,
preview,
toolchain_preference,
toolchain_fetch,
allow_existing,
exclude_newer,
native_tls,
@ -121,6 +123,7 @@ async fn venv_impl(
seed: bool,
preview: PreviewMode,
toolchain_preference: ToolchainPreference,
toolchain_fetch: ToolchainFetch,
allow_existing: bool,
exclude_newer: Option<ExcludeNewer>,
native_tls: bool,
@ -141,7 +144,8 @@ async fn venv_impl(
interpreter_request,
EnvironmentPreference::OnlySystem,
toolchain_preference,
client_builder,
toolchain_fetch,
&client_builder,
cache,
)
.await

View file

@ -617,6 +617,7 @@ async fn run() -> Result<ExitStatus> {
&args.name,
args.settings.python.as_deref(),
globals.toolchain_preference,
globals.toolchain_fetch,
args.settings.link_mode,
&args.settings.index_locations,
args.settings.index_strategy,
@ -659,6 +660,7 @@ async fn run() -> Result<ExitStatus> {
globals.isolated,
globals.preview,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.connectivity,
Concurrency::default(),
globals.native_tls,
@ -681,6 +683,7 @@ async fn run() -> Result<ExitStatus> {
args.modifications,
args.python,
globals.toolchain_preference,
globals.toolchain_fetch,
args.settings,
globals.preview,
globals.connectivity,
@ -704,6 +707,7 @@ async fn run() -> Result<ExitStatus> {
args.settings,
globals.preview,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.connectivity,
Concurrency::default(),
globals.native_tls,
@ -733,6 +737,7 @@ async fn run() -> Result<ExitStatus> {
args.python,
args.settings,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.preview,
globals.connectivity,
Concurrency::default(),
@ -756,6 +761,7 @@ async fn run() -> Result<ExitStatus> {
args.package,
args.python,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.preview,
globals.connectivity,
Concurrency::default(),
@ -796,6 +802,7 @@ async fn run() -> Result<ExitStatus> {
globals.isolated,
globals.preview,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.connectivity,
Concurrency::default(),
globals.native_tls,
@ -823,6 +830,7 @@ async fn run() -> Result<ExitStatus> {
args.settings,
globals.preview,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.connectivity,
Concurrency::default(),
globals.native_tls,
@ -870,6 +878,7 @@ async fn run() -> Result<ExitStatus> {
args.all_versions,
args.all_platforms,
globals.toolchain_preference,
globals.toolchain_fetch,
globals.preview,
&cache,
printer,

View file

@ -31,7 +31,7 @@ use uv_settings::{
Combine, FilesystemOptions, InstallerOptions, Options, PipOptions, ResolverInstallerOptions,
ResolverOptions,
};
use uv_toolchain::{Prefix, PythonVersion, Target, ToolchainPreference};
use uv_toolchain::{Prefix, PythonVersion, Target, ToolchainFetch, ToolchainPreference};
use crate::commands::pip::operations::Modifications;
@ -48,6 +48,7 @@ pub(crate) struct GlobalSettings {
pub(crate) show_settings: bool,
pub(crate) preview: PreviewMode,
pub(crate) toolchain_preference: ToolchainPreference,
pub(crate) toolchain_fetch: ToolchainFetch,
}
impl GlobalSettings {
@ -114,6 +115,10 @@ impl GlobalSettings {
.toolchain_preference
.combine(workspace.and_then(|workspace| workspace.globals.toolchain_preference))
.unwrap_or(default_toolchain_preference),
toolchain_fetch: args
.toolchain_fetch
.combine(workspace.and_then(|workspace| workspace.globals.toolchain_fetch))
.unwrap_or_default(),
}
}
}

View file

@ -711,7 +711,7 @@ pub fn python_toolchains_for_versions(
if let Ok(toolchain) = Toolchain::find(
&ToolchainRequest::parse(python_version),
EnvironmentPreference::OnlySystem,
ToolchainPreference::PreferInstalledManaged,
ToolchainPreference::PreferManaged,
&cache,
) {
toolchain.into_interpreter().sys_executable().to_owned()

View file

@ -2039,8 +2039,8 @@ fn lock_requires_python() -> Result<()> {
.collect();
// Install from the lockfile.
// Note we need `--offline` otherwise we'll just fetch a 3.12 interpreter!
uv_snapshot!(filters, context38.sync().arg("--offline"), @r###"
// Note we need to disable toolchain fetches or we'll just download 3.12
uv_snapshot!(filters, context38.sync().arg("--toolchain-fetch").arg("manual"), @r###"
success: false
exit_code: 2
----- stdout -----

View file

@ -448,7 +448,6 @@ fn prune() {
threadpoolctl v3.4.0
----- stderr -----
"###
);
@ -472,7 +471,6 @@ fn prune() {
threadpoolctl v3.4.0
----- stderr -----
"###
);
@ -495,7 +493,6 @@ fn prune() {
threadpoolctl v3.4.0
----- stderr -----
"###
);
}
@ -1350,7 +1347,6 @@ fn with_editable() {
iniconfig v2.0.1.dev6+g9cae431
----- stderr -----
"###
);
}

View file

@ -58,6 +58,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -188,6 +189,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -319,6 +321,7 @@ fn resolve_uv_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -482,6 +485,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -614,6 +618,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -732,6 +737,7 @@ fn resolve_pyproject_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -887,6 +893,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1042,6 +1049,7 @@ fn resolve_index_url() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1242,6 +1250,7 @@ fn resolve_find_links() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1391,6 +1400,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1515,6 +1525,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1667,6 +1678,7 @@ fn resolve_top_level() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1843,6 +1855,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -1957,6 +1970,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -2071,6 +2085,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -2187,6 +2202,7 @@ fn resolve_user_configuration() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,
@ -2328,6 +2344,7 @@ fn resolve_poetry_toml() -> anyhow::Result<()> {
show_settings: true,
preview: Disabled,
toolchain_preference: OnlySystem,
toolchain_fetch: Automatic,
}
CacheSettings {
no_cache: false,

40
uv.schema.json generated
View file

@ -235,6 +235,16 @@
"$ref": "#/definitions/Source"
}
},
"toolchain-fetch": {
"anyOf": [
{
"$ref": "#/definitions/ToolchainFetch"
},
{
"type": "null"
}
]
},
"toolchain-preference": {
"anyOf": [
{
@ -1187,38 +1197,56 @@
}
}
},
"ToolchainFetch": {
"oneOf": [
{
"description": "Automatically fetch managed toolchains when needed.",
"type": "string",
"enum": [
"automatic"
]
},
{
"description": "Do not automatically fetch managed toolchains; require explicit installation.",
"type": "string",
"enum": [
"manual"
]
}
]
},
"ToolchainPreference": {
"oneOf": [
{
"description": "Only use managed interpreters, never use system interpreters.",
"description": "Only use managed toolchains, never use system toolchains.",
"type": "string",
"enum": [
"only-managed"
]
},
{
"description": "Prefer installed managed interpreters, but use system interpreters if not found. If neither can be found, download a managed interpreter.",
"description": "Prefer installed toolchains, only download managed toolchains if no system toolchain is found.",
"type": "string",
"enum": [
"prefer-installed-managed"
"installed"
]
},
{
"description": "Prefer managed interpreters, even if one needs to be downloaded, but use system interpreters if found.",
"description": "Prefer managed toolchains over system toolchains, even if one needs to be downloaded.",
"type": "string",
"enum": [
"prefer-managed"
]
},
{
"description": "Prefer system interpreters, only use managed interpreters if no system interpreter is found.",
"description": "Prefer system toolchains, only use managed toolchains if no system toolchain is found.",
"type": "string",
"enum": [
"prefer-system"
]
},
{
"description": "Only use system interpreters, never use managed interpreters.",
"description": "Only use system toolchains, never use managed toolchains.",
"type": "string",
"enum": [
"only-system"