Files
uv/crates/uv-python/src/installation.rs
T

422 lines
14 KiB
Rust
Raw Normal View History

use std::fmt;
use std::str::FromStr;
use tracing::{debug, info};
2024-06-07 15:20:28 -04:00
use uv_cache::Cache;
use uv_client::BaseClientBuilder;
2024-10-08 23:20:58 +02:00
use uv_pep440::{Prerelease, Version};
2024-06-07 15:20:28 -04:00
use crate::discovery::{
2024-07-03 08:44:29 -04:00
find_best_python_installation, find_python_installation, EnvironmentPreference, PythonRequest,
2024-06-07 15:20:28 -04:00
};
use crate::downloads::{DownloadResult, ManagedPythonDownload, PythonDownloadRequest, Reporter};
use crate::implementation::LenientImplementationName;
2024-07-03 08:44:29 -04:00
use crate::managed::{ManagedPythonInstallation, ManagedPythonInstallations};
use crate::platform::{Arch, Libc, Os};
use crate::{
downloads, Error, ImplementationName, Interpreter, PythonDownloads, PythonPreference,
PythonSource, PythonVariant, PythonVersion,
};
2024-06-07 15:20:28 -04:00
/// A Python interpreter and accompanying tools.
#[derive(Clone, Debug)]
2024-07-03 08:44:29 -04:00
pub struct PythonInstallation {
2024-06-07 15:20:28 -04:00
// Public in the crate for test assertions
2024-07-03 08:44:29 -04:00
pub(crate) source: PythonSource,
2024-06-07 15:20:28 -04:00
pub(crate) interpreter: Interpreter,
}
2024-07-03 08:44:29 -04:00
impl PythonInstallation {
/// Create a new [`PythonInstallation`] from a source, interpreter tuple.
pub(crate) fn from_tuple(tuple: (PythonSource, Interpreter)) -> Self {
let (source, interpreter) = tuple;
Self {
source,
interpreter,
}
}
2024-07-03 08:44:29 -04:00
/// Find an installed [`PythonInstallation`].
2024-06-07 15:20:28 -04:00
///
2024-07-03 08:44:29 -04:00
/// This is the standard interface for discovering a Python installation for creating
2024-06-20 13:54:17 -04:00
/// an environment. If interested in finding an existing environment, see
/// [`PythonEnvironment::find`] instead.
2024-06-07 15:20:28 -04:00
///
2024-06-20 13:54:17 -04:00
/// Note we still require an [`EnvironmentPreference`] as this can either bypass virtual environments
/// or prefer them. In most cases, this should be [`EnvironmentPreference::OnlySystem`]
/// but if you want to allow an interpreter from a virtual environment if it satisfies the request,
/// then use [`EnvironmentPreference::Any`].
///
2024-07-03 08:44:29 -04:00
/// See [`find_installation`] for implementation details.
2024-06-07 15:20:28 -04:00
pub fn find(
2024-07-03 08:44:29 -04:00
request: &PythonRequest,
environments: EnvironmentPreference,
2024-07-03 08:44:29 -04:00
preference: PythonPreference,
2024-06-07 15:20:28 -04:00
cache: &Cache,
) -> Result<Self, Error> {
2024-07-03 08:44:29 -04:00
let installation = find_python_installation(request, environments, preference, cache)??;
Ok(installation)
2024-06-07 15:20:28 -04:00
}
2024-07-03 08:44:29 -04:00
/// Find an installed [`PythonInstallation`] that satisfies a requested version, if the request cannot
/// be satisfied, fallback to the best available Python installation.
2024-06-07 15:20:28 -04:00
pub fn find_best(
2024-07-03 08:44:29 -04:00
request: &PythonRequest,
environments: EnvironmentPreference,
2024-07-03 08:44:29 -04:00
preference: PythonPreference,
2024-06-07 15:20:28 -04:00
cache: &Cache,
) -> Result<Self, Error> {
2024-07-03 08:44:29 -04:00
Ok(find_best_python_installation(
request,
environments,
preference,
cache,
)??)
2024-06-07 15:20:28 -04:00
}
2024-07-03 08:44:29 -04:00
/// Find or fetch a [`PythonInstallation`].
///
2024-07-03 08:44:29 -04:00
/// Unlike [`PythonInstallation::find`], if the required Python is not installed it will be installed automatically.
pub async fn find_or_download<'a>(
request: Option<&PythonRequest>,
environments: EnvironmentPreference,
2024-07-03 08:44:29 -04:00
preference: PythonPreference,
python_downloads: PythonDownloads,
client_builder: &BaseClientBuilder<'a>,
cache: &Cache,
reporter: Option<&dyn Reporter>,
) -> Result<Self, Error> {
let request = request.unwrap_or_else(|| &PythonRequest::Default);
2024-07-03 08:44:29 -04:00
// Search for the installation
match Self::find(request, environments, preference, cache) {
Ok(venv) => Ok(venv),
// If missing and allowed, perform a fetch
Err(Error::MissingPython(err))
if preference.allows_managed()
&& python_downloads.is_automatic()
&& client_builder.connectivity.is_online() =>
{
if let Some(request) = PythonDownloadRequest::from_request(request) {
debug!("Requested Python not found, checking for available download...");
match Self::fetch(request.fill()?, client_builder, cache, reporter).await {
Ok(installation) => Ok(installation),
Err(Error::Download(downloads::Error::NoDownloadFound(_))) => {
Err(Error::MissingPython(err))
}
Err(err) => Err(err),
}
} else {
Err(Error::MissingPython(err))
}
}
Err(err) => Err(err),
}
}
2024-07-03 08:44:29 -04:00
/// Download and install the requested installation.
pub async fn fetch<'a>(
request: PythonDownloadRequest,
client_builder: &BaseClientBuilder<'a>,
cache: &Cache,
reporter: Option<&dyn Reporter>,
) -> Result<Self, Error> {
2024-07-03 08:44:29 -04:00
let installations = ManagedPythonInstallations::from_settings()?.init()?;
let installations_dir = installations.root();
let cache_dir = installations.cache();
let _lock = installations.lock().await?;
2024-07-03 08:44:29 -04:00
let download = ManagedPythonDownload::from_request(&request)?;
let client = client_builder.build();
2024-07-03 08:44:29 -04:00
info!("Fetching requested Python...");
let result = download
.fetch(&client, installations_dir, &cache_dir, false, reporter)
.await?;
let path = match result {
DownloadResult::AlreadyAvailable(path) => path,
DownloadResult::Fetched(path) => path,
};
2024-07-03 08:44:29 -04:00
let installed = ManagedPythonInstallation::new(path)?;
installed.ensure_externally_managed()?;
installed.ensure_canonical_executables()?;
Ok(Self {
2024-07-03 08:44:29 -04:00
source: PythonSource::Managed,
interpreter: Interpreter::query(installed.executable(), cache)?,
})
}
2024-07-03 08:44:29 -04:00
/// Create a [`PythonInstallation`] from an existing [`Interpreter`].
2024-06-07 15:20:28 -04:00
pub fn from_interpreter(interpreter: Interpreter) -> Self {
Self {
2024-07-03 08:44:29 -04:00
source: PythonSource::ProvidedPath,
2024-06-07 15:20:28 -04:00
interpreter,
}
}
2024-07-03 08:44:29 -04:00
/// Return the [`PythonSource`] of the Python installation, indicating where it was found.
pub fn source(&self) -> &PythonSource {
2024-06-07 15:20:28 -04:00
&self.source
}
2024-07-03 08:44:29 -04:00
pub fn key(&self) -> PythonInstallationKey {
self.interpreter.key()
}
2024-07-03 08:44:29 -04:00
/// Return the Python [`Version`] of the Python installation as reported by its interpreter.
pub fn python_version(&self) -> &Version {
self.interpreter.python_version()
}
2024-07-03 08:44:29 -04:00
/// Return the [`LenientImplementationName`] of the Python installation as reported by its interpreter.
pub fn implementation(&self) -> LenientImplementationName {
LenientImplementationName::from(self.interpreter.implementation_name())
}
/// Whether this is a CPython installation.
///
/// Returns false if it is an alternative implementation, e.g., PyPy.
pub(crate) fn is_alternative_implementation(&self) -> bool {
!matches!(
self.implementation(),
LenientImplementationName::Known(ImplementationName::CPython)
)
}
2024-07-03 08:44:29 -04:00
/// Return the [`Arch`] of the Python installation as reported by its interpreter.
pub fn arch(&self) -> Arch {
self.interpreter.arch()
}
2024-07-03 08:44:29 -04:00
/// Return the [`Libc`] of the Python installation as reported by its interpreter.
pub fn libc(&self) -> Libc {
self.interpreter.libc()
}
2024-07-03 08:44:29 -04:00
/// Return the [`Os`] of the Python installation as reported by its interpreter.
pub fn os(&self) -> Os {
self.interpreter.os()
}
2024-07-03 08:44:29 -04:00
/// Return the [`Interpreter`] for the Python installation.
2024-06-07 15:20:28 -04:00
pub fn interpreter(&self) -> &Interpreter {
&self.interpreter
}
pub fn into_interpreter(self) -> Interpreter {
self.interpreter
}
}
#[derive(Error, Debug)]
2024-07-03 08:44:29 -04:00
pub enum PythonInstallationKeyError {
#[error("Failed to parse Python installation key `{0}`: {1}")]
ParseError(String, String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2024-07-03 08:44:29 -04:00
pub struct PythonInstallationKey {
pub(crate) implementation: LenientImplementationName,
pub(crate) major: u8,
pub(crate) minor: u8,
pub(crate) patch: u8,
2024-10-08 23:20:58 +02:00
pub(crate) prerelease: Option<Prerelease>,
pub(crate) os: Os,
pub(crate) arch: Arch,
pub(crate) libc: Libc,
pub(crate) variant: PythonVariant,
}
2024-07-03 08:44:29 -04:00
impl PythonInstallationKey {
pub fn new(
implementation: LenientImplementationName,
major: u8,
minor: u8,
patch: u8,
2024-10-08 23:20:58 +02:00
prerelease: Option<Prerelease>,
os: Os,
arch: Arch,
libc: Libc,
variant: PythonVariant,
) -> Self {
Self {
implementation,
major,
minor,
patch,
2024-10-08 23:20:58 +02:00
prerelease,
os,
arch,
libc,
variant,
}
}
pub fn new_from_version(
implementation: LenientImplementationName,
version: &PythonVersion,
os: Os,
arch: Arch,
libc: Libc,
variant: PythonVariant,
) -> Self {
Self {
implementation,
major: version.major(),
minor: version.minor(),
patch: version.patch().unwrap_or_default(),
2024-10-08 23:20:58 +02:00
prerelease: version.pre(),
os,
arch,
libc,
variant,
}
}
pub fn implementation(&self) -> &LenientImplementationName {
&self.implementation
}
pub fn version(&self) -> PythonVersion {
PythonVersion::from_str(&format!(
"{}.{}.{}{}",
2024-10-08 23:20:58 +02:00
self.major,
self.minor,
self.patch,
self.prerelease
.map(|pre| pre.to_string())
.unwrap_or_default()
))
.expect("Python installation keys must have valid Python versions")
}
pub fn arch(&self) -> &Arch {
&self.arch
}
pub fn os(&self) -> &Os {
&self.os
}
pub fn libc(&self) -> &Libc {
&self.libc
}
/// Return a canonical name for a versioned executable.
pub fn versioned_executable_name(&self) -> String {
format!(
"python{maj}.{min}{var}{exe}",
maj = self.major,
min = self.minor,
var = self.variant.suffix(),
exe = std::env::consts::EXE_SUFFIX
)
}
}
2024-07-03 08:44:29 -04:00
impl fmt::Display for PythonInstallationKey {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let variant = match self.variant {
PythonVariant::Default => String::new(),
PythonVariant::Freethreaded => format!("+{}", self.variant),
};
write!(
f,
"{}-{}.{}.{}{}{}-{}-{}-{}",
self.implementation,
self.major,
self.minor,
self.patch,
2024-10-08 23:20:58 +02:00
self.prerelease
.map(|pre| pre.to_string())
.unwrap_or_default(),
variant,
self.os,
self.arch,
self.libc
)
}
}
2024-07-03 08:44:29 -04:00
impl FromStr for PythonInstallationKey {
type Err = PythonInstallationKeyError;
fn from_str(key: &str) -> Result<Self, Self::Err> {
let parts = key.split('-').collect::<Vec<_>>();
let [implementation, version, os, arch, libc] = parts.as_slice() else {
2024-07-03 08:44:29 -04:00
return Err(PythonInstallationKeyError::ParseError(
key.to_string(),
"not enough `-`-separated values".to_string(),
));
};
let implementation = LenientImplementationName::from(*implementation);
let os = Os::from_str(os).map_err(|err| {
2024-07-03 08:44:29 -04:00
PythonInstallationKeyError::ParseError(key.to_string(), format!("invalid OS: {err}"))
})?;
let arch = Arch::from_str(arch).map_err(|err| {
2024-07-03 08:44:29 -04:00
PythonInstallationKeyError::ParseError(
key.to_string(),
format!("invalid architecture: {err}"),
)
})?;
let libc = Libc::from_str(libc).map_err(|err| {
2024-07-03 08:44:29 -04:00
PythonInstallationKeyError::ParseError(key.to_string(), format!("invalid libc: {err}"))
})?;
let (version, variant) = match version.split_once('+') {
Some((version, variant)) => {
let variant = PythonVariant::from_str(variant).map_err(|()| {
PythonInstallationKeyError::ParseError(
key.to_string(),
format!("invalid Python variant: {variant}"),
)
})?;
(version, variant)
}
None => (*version, PythonVariant::Default),
};
let version = PythonVersion::from_str(version).map_err(|err| {
PythonInstallationKeyError::ParseError(
key.to_string(),
format!("invalid Python version: {err}"),
)
})?;
Ok(Self::new_from_version(
implementation,
&version,
os,
arch,
libc,
variant,
))
}
}
2024-07-03 08:44:29 -04:00
impl PartialOrd for PythonInstallationKey {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
2024-07-03 08:44:29 -04:00
impl Ord for PythonInstallationKey {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.implementation
.cmp(&other.implementation)
.then_with(|| self.version().cmp(&other.version()))
.then_with(|| self.os.to_string().cmp(&other.os.to_string()))
.then_with(|| self.arch.to_string().cmp(&other.arch.to_string()))
.then_with(|| self.libc.to_string().cmp(&other.libc.to_string()))
.then_with(|| self.variant.cmp(&other.variant))
}
}