2024-06-17 14:11:24 -04:00
use std ::fmt ;
2025-06-20 10:17:13 -04:00
use std ::hash ::{ Hash , Hasher } ;
2024-06-17 14:11:24 -04:00
use std ::str ::FromStr ;
2025-06-20 10:17:13 -04:00
use indexmap ::IndexMap ;
use ref_cast ::RefCast ;
2024-06-10 10:10:45 -04:00
use tracing ::{ debug , info } ;
2024-06-07 15:20:28 -04:00
use uv_cache ::Cache ;
2024-08-12 16:15:30 -04:00
use uv_client ::BaseClientBuilder ;
2025-07-25 11:01:57 -05:00
use uv_configuration ::Preview ;
2024-10-08 23:20:58 +02:00
use uv_pep440 ::{ Prerelease , Version } ;
2025-07-28 14:12:04 -05:00
use uv_platform ::{ Arch , Libc , Os } ;
2024-06-07 15:20:28 -04:00
2024-06-07 17:56:25 -04:00
use crate ::discovery ::{
2025-05-18 19:38:43 -04:00
EnvironmentPreference , PythonRequest , find_best_python_installation , find_python_installation ,
2024-06-07 15:20:28 -04:00
} ;
2024-07-08 04:01:35 +08:00
use crate ::downloads ::{ DownloadResult , ManagedPythonDownload , PythonDownloadRequest , Reporter } ;
2024-06-13 11:25:30 -04:00
use crate ::implementation ::LenientImplementationName ;
2024-07-03 08:44:29 -04:00
use crate ::managed ::{ ManagedPythonInstallation , ManagedPythonInstallations } ;
2024-07-09 01:29:55 -04:00
use crate ::{
2025-05-18 19:38:43 -04:00
Error , ImplementationName , Interpreter , PythonDownloads , PythonPreference , PythonSource ,
PythonVariant , PythonVersion , downloads ,
2024-07-09 01:29:55 -04:00
} ;
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 {
2024-06-13 11:25:30 -04:00
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 ,
2024-06-20 09:57:05 -04:00
environments : EnvironmentPreference ,
2024-07-03 08:44:29 -04:00
preference : PythonPreference ,
2024-06-07 15:20:28 -04:00
cache : & Cache ,
2025-07-25 11:01:57 -05:00
preview : Preview ,
2024-06-07 15:20:28 -04:00
) -> Result < Self , Error > {
2025-06-20 10:17:13 -04:00
let installation =
find_python_installation ( request , environments , preference , cache , preview ) ? ? ;
2024-07-03 08:44:29 -04:00
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 ,
2024-06-20 09:57:05 -04:00
environments : EnvironmentPreference ,
2024-07-03 08:44:29 -04:00
preference : PythonPreference ,
2024-06-07 15:20:28 -04:00
cache : & Cache ,
2025-07-25 11:01:57 -05:00
preview : Preview ,
2024-06-07 15:20:28 -04:00
) -> Result < Self , Error > {
2024-07-03 08:44:29 -04:00
Ok ( find_best_python_installation (
2024-06-20 09:57:05 -04:00
request ,
environments ,
preference ,
cache ,
2025-06-20 10:17:13 -04:00
preview ,
2024-06-20 09:57:05 -04:00
) ? ? )
2024-06-07 15:20:28 -04:00
}
2024-07-03 08:44:29 -04:00
/// Find or fetch a [`PythonInstallation`].
2024-06-10 10:10:45 -04:00
///
2024-07-03 08:44:29 -04:00
/// Unlike [`PythonInstallation::find`], if the required Python is not installed it will be installed automatically.
2025-01-11 22:19:33 -05:00
pub async fn find_or_download (
2024-08-30 09:49:17 -04:00
request : Option < & PythonRequest > ,
2024-06-20 09:57:05 -04:00
environments : EnvironmentPreference ,
2024-07-03 08:44:29 -04:00
preference : PythonPreference ,
2024-08-09 13:10:19 -05:00
python_downloads : PythonDownloads ,
2025-01-11 22:19:33 -05:00
client_builder : & BaseClientBuilder < '_ > ,
2024-06-10 10:10:45 -04:00
cache : & Cache ,
2024-07-08 04:01:35 +08:00
reporter : Option < & dyn Reporter > ,
2024-11-20 09:42:42 -05:00
python_install_mirror : Option < & str > ,
pypy_install_mirror : Option < & str > ,
2025-04-30 22:52:11 +03:00
python_downloads_json_url : Option < & str > ,
2025-07-25 11:01:57 -05:00
preview : Preview ,
2024-06-10 10:10:45 -04:00
) -> Result < Self , Error > {
2024-12-11 10:06:19 -06:00
let request = request . unwrap_or ( & PythonRequest ::Default ) ;
2024-07-01 21:54:24 -04:00
2024-07-03 08:44:29 -04:00
// Search for the installation
2025-06-20 10:17:13 -04:00
let err = match Self ::find ( request , environments , preference , cache , preview ) {
2025-01-23 16:37:02 -06:00
Ok ( installation ) = > return Ok ( installation ) ,
Err ( err ) = > err ,
} ;
match err {
// If Python is missing, we should attempt a download
2025-07-10 12:06:24 -05:00
Error ::MissingPython ( .. ) = > { }
2025-01-23 16:37:02 -06:00
// If we raised a non-critical error, we should attempt a download
Error ::Discovery ( ref err ) if ! err . is_critical ( ) = > { }
// Otherwise, this is fatal
_ = > return Err ( err ) ,
}
// If we can't convert the request to a download, throw the original error
2025-07-10 12:06:24 -05:00
let Some ( download_request ) = PythonDownloadRequest ::from_request ( request ) else {
return Err ( err ) ;
} ;
let downloads_enabled = preference . allows_managed ( )
& & python_downloads . is_automatic ( )
& & client_builder . connectivity . is_online ( ) ;
let download = download_request . clone ( ) . fill ( ) . map ( | request | {
ManagedPythonDownload ::from_request ( & request , python_downloads_json_url )
} ) ;
// Regardless of whether downloads are enabled, we want to determine if the download is
// available to power error messages. However, if downloads aren't enabled, we don't want to
// report any errors related to them.
let download = match download {
Ok ( Ok ( download ) ) = > Some ( download ) ,
// If the download cannot be found, return the _original_ discovery error
Ok ( Err ( downloads ::Error ::NoDownloadFound ( _ ) ) ) = > {
if downloads_enabled {
debug! ( " No downloads are available for {request} " ) ;
return Err ( err ) ;
}
None
}
Err ( err ) | Ok ( Err ( err ) ) = > {
if downloads_enabled {
// We failed to determine the platform information
return Err ( err . into ( ) ) ;
}
None
}
} ;
let Some ( download ) = download else {
// N.B. We should only be in this case when downloads are disabled; when downloads are
// enabled, we should fail eagerly when something goes wrong with the download.
debug_assert! ( ! downloads_enabled ) ;
2025-01-23 16:37:02 -06:00
return Err ( err ) ;
} ;
2025-07-10 12:06:24 -05:00
// If the download is available, but not usable, we attach a hint to the original error.
if ! downloads_enabled {
let for_request = match request {
PythonRequest ::Default | PythonRequest ::Any = > String ::new ( ) ,
_ = > format! ( " for {request} " ) ,
} ;
match python_downloads {
PythonDownloads ::Automatic = > { }
PythonDownloads ::Manual = > {
return Err ( err . with_missing_python_hint ( format! (
" A managed Python download is available {for_request} , but Python downloads are set to 'manual', use `uv python install {} ` to install the required version " ,
request . to_canonical_string ( ) ,
) ) ) ;
}
PythonDownloads ::Never = > {
return Err ( err . with_missing_python_hint ( format! (
" A managed Python download is available {for_request} , but Python downloads are set to 'never' "
) ) ) ;
}
}
match preference {
PythonPreference ::OnlySystem = > {
return Err ( err . with_missing_python_hint ( format! (
" A managed Python download is available {for_request} , but the Python preference is set to 'only system' "
) ) ) ;
}
PythonPreference ::Managed
| PythonPreference ::OnlyManaged
| PythonPreference ::System = > { }
}
if ! client_builder . connectivity . is_online ( ) {
return Err ( err . with_missing_python_hint ( format! (
" A managed Python download is available {for_request} , but uv is set to offline mode "
) ) ) ;
}
return Err ( err ) ;
}
Self ::fetch (
download ,
2025-01-23 16:37:02 -06:00
client_builder ,
cache ,
reporter ,
python_install_mirror ,
pypy_install_mirror ,
2025-06-20 10:17:13 -04:00
preview ,
2025-01-23 16:37:02 -06:00
)
. await
2024-06-10 10:10:45 -04:00
}
2024-07-03 08:44:29 -04:00
/// Download and install the requested installation.
2025-01-11 22:19:33 -05:00
pub async fn fetch (
2025-07-10 12:06:24 -05:00
download : & 'static ManagedPythonDownload ,
2025-01-11 22:19:33 -05:00
client_builder : & BaseClientBuilder < '_ > ,
2024-06-10 10:10:45 -04:00
cache : & Cache ,
2024-07-08 04:01:35 +08:00
reporter : Option < & dyn Reporter > ,
2024-11-20 09:42:42 -05:00
python_install_mirror : Option < & str > ,
pypy_install_mirror : Option < & str > ,
2025-07-25 11:01:57 -05:00
preview : Preview ,
2024-06-10 10:10:45 -04:00
) -> Result < Self , Error > {
2024-12-10 19:04:31 +02:00
let installations = ManagedPythonInstallations ::from_settings ( None ) ? . init ( ) ? ;
2024-07-03 08:44:29 -04:00
let installations_dir = installations . root ( ) ;
2024-12-10 08:41:16 -06:00
let scratch_dir = installations . scratch ( ) ;
2024-08-29 11:16:14 -05:00
let _lock = installations . lock ( ) . await ? ;
2024-06-10 10:10:45 -04:00
let client = client_builder . build ( ) ;
2024-07-03 08:44:29 -04:00
info! ( " Fetching requested Python... " ) ;
2024-08-04 22:32:24 -04:00
let result = download
2024-11-20 09:42:42 -05:00
. fetch_with_retry (
2024-11-14 03:08:55 +11:00
& client ,
installations_dir ,
2024-12-10 08:41:16 -06:00
& scratch_dir ,
2024-11-14 03:08:55 +11:00
false ,
python_install_mirror ,
pypy_install_mirror ,
reporter ,
)
2024-08-04 22:32:24 -04:00
. await ? ;
2024-06-10 10:10:45 -04:00
let path = match result {
DownloadResult ::AlreadyAvailable ( path ) = > path ,
DownloadResult ::Fetched ( path ) = > path ,
} ;
2025-01-23 15:13:41 +01:00
let installed = ManagedPythonInstallation ::new ( path , download ) ;
2024-06-17 11:25:34 -04:00
installed . ensure_externally_managed ( ) ? ;
2024-12-13 14:36:22 -05:00
installed . ensure_sysconfig_patched ( ) ? ;
2024-10-17 18:27:55 -05:00
installed . ensure_canonical_executables ( ) ? ;
2025-06-20 10:17:13 -04:00
let minor_version = installed . minor_version_key ( ) ;
let highest_patch = installations
. find_all ( ) ?
. filter ( | installation | installation . minor_version_key ( ) = = minor_version )
. filter_map ( | installation | installation . version ( ) . patch ( ) )
. fold ( 0 , std ::cmp ::max ) ;
if installed
. version ( )
. patch ( )
. is_some_and ( | p | p > = highest_patch )
{
installed . ensure_minor_version_link ( preview ) ? ;
}
2025-01-15 21:11:54 +01:00
if let Err ( e ) = installed . ensure_dylib_patched ( ) {
e . warn_user ( & installed ) ;
}
2024-06-10 10:10:45 -04:00
Ok ( Self {
2024-07-03 08:44:29 -04:00
source : PythonSource ::Managed ,
2025-01-23 15:13:41 +01:00
interpreter : Interpreter ::query ( installed . executable ( false ) , cache ) ? ,
2024-06-10 10:10:45 -04:00
} )
}
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 {
2024-07-23 16:29:15 -04:00
self . interpreter . key ( )
2024-06-13 11:25:30 -04:00
}
2024-07-03 08:44:29 -04:00
/// Return the Python [`Version`] of the Python installation as reported by its interpreter.
2024-06-13 11:25:30 -04:00
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.
2024-06-13 11:25:30 -04:00
pub fn implementation ( & self ) -> LenientImplementationName {
LenientImplementationName ::from ( self . interpreter . implementation_name ( ) )
}
2024-09-24 12:52:15 -05:00
/// 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.
2024-06-13 11:25:30 -04:00
pub fn arch ( & self ) -> Arch {
2024-07-23 16:29:15 -04:00
self . interpreter . arch ( )
2024-06-13 11:25:30 -04:00
}
2024-07-03 08:44:29 -04:00
/// Return the [`Libc`] of the Python installation as reported by its interpreter.
2024-06-13 11:25:30 -04:00
pub fn libc ( & self ) -> Libc {
2024-07-23 16:29:15 -04:00
self . interpreter . libc ( )
2024-06-13 11:25:30 -04:00
}
2024-07-03 08:44:29 -04:00
/// Return the [`Os`] of the Python installation as reported by its interpreter.
2024-06-13 11:25:30 -04:00
pub fn os ( & self ) -> Os {
2024-07-23 16:29:15 -04:00
self . interpreter . os ( )
2024-06-13 11:25:30 -04:00
}
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
}
2024-12-23 19:29:37 -05:00
/// Consume the [`PythonInstallation`] and return the [`Interpreter`].
2024-06-07 15:20:28 -04:00
pub fn into_interpreter ( self ) -> Interpreter {
self . interpreter
}
}
2024-06-17 14:11:24 -04:00
#[ derive(Error, Debug) ]
2024-07-03 08:44:29 -04:00
pub enum PythonInstallationKeyError {
#[ error( " Failed to parse Python installation key `{0}`: {1} " ) ]
2024-06-17 14:11:24 -04:00
ParseError ( String , String ) ,
}
2024-07-06 11:05:17 +08:00
#[ derive(Debug, Clone, PartialEq, Eq, Hash) ]
2024-07-03 08:44:29 -04:00
pub struct PythonInstallationKey {
2024-06-17 14:11:24 -04:00
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 > ,
2024-06-17 14:11:24 -04:00
pub ( crate ) os : Os ,
pub ( crate ) arch : Arch ,
pub ( crate ) libc : Libc ,
2024-10-14 15:18:52 -05:00
pub ( crate ) variant : PythonVariant ,
2024-06-17 14:11:24 -04:00
}
2024-07-03 08:44:29 -04:00
impl PythonInstallationKey {
2024-06-17 14:11:24 -04:00
pub fn new (
implementation : LenientImplementationName ,
major : u8 ,
minor : u8 ,
patch : u8 ,
2024-10-08 23:20:58 +02:00
prerelease : Option < Prerelease > ,
2024-06-17 14:11:24 -04:00
os : Os ,
arch : Arch ,
libc : Libc ,
2024-10-14 15:18:52 -05:00
variant : PythonVariant ,
2024-06-17 14:11:24 -04:00
) -> Self {
Self {
implementation ,
major ,
minor ,
patch ,
2024-10-08 23:20:58 +02:00
prerelease ,
2024-09-10 14:36:16 -05:00
os ,
arch ,
libc ,
2024-10-14 15:18:52 -05:00
variant ,
2024-09-10 14:36:16 -05:00
}
}
2025-04-07 20:55:00 +03:00
pub fn new_from_version (
2024-09-10 14:36:16 -05:00
implementation : LenientImplementationName ,
version : & PythonVersion ,
os : Os ,
arch : Arch ,
libc : Libc ,
2024-10-14 15:18:52 -05:00
variant : PythonVariant ,
2024-09-10 14:36:16 -05:00
) -> 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 ( ) ,
2024-06-17 14:11:24 -04:00
os ,
arch ,
libc ,
2024-10-14 15:18:52 -05:00
variant ,
2024-06-17 14:11:24 -04:00
}
}
pub fn implementation ( & self ) -> & LenientImplementationName {
& self . implementation
}
2024-09-10 14:36:16 -05:00
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 ( )
2024-09-10 14:36:16 -05:00
) )
. expect ( " Python installation keys must have valid Python versions " )
2024-06-17 14:11:24 -04:00
}
2025-01-23 15:13:41 +01:00
/// The version in `x.y.z` format.
pub fn sys_version ( & self ) -> String {
format! ( " {} . {} . {} " , self . major , self . minor , self . patch )
}
2025-06-20 10:17:13 -04:00
pub fn major ( & self ) -> u8 {
self . major
}
pub fn minor ( & self ) -> u8 {
self . minor
}
2024-06-17 14:11:24 -04:00
pub fn arch ( & self ) -> & Arch {
& self . arch
}
pub fn os ( & self ) -> & Os {
& self . os
}
pub fn libc ( & self ) -> & Libc {
& self . libc
}
2024-10-30 09:13:20 -05:00
2024-12-06 08:28:28 -06:00
pub fn variant ( & self ) -> & PythonVariant {
& self . variant
}
2024-12-02 19:04:57 -06:00
/// Return a canonical name for a minor versioned executable.
pub fn executable_name_minor ( & self ) -> String {
2024-10-30 09:13:20 -05:00
format! (
" python {maj} . {min} {var} {exe} " ,
maj = self . major ,
min = self . minor ,
var = self . variant . suffix ( ) ,
exe = std ::env ::consts ::EXE_SUFFIX
)
}
2024-12-02 19:04:57 -06:00
/// Return a canonical name for a major versioned executable.
pub fn executable_name_major ( & self ) -> String {
format! (
" python {maj} {var} {exe} " ,
maj = self . major ,
var = self . variant . suffix ( ) ,
exe = std ::env ::consts ::EXE_SUFFIX
)
}
/// Return a canonical name for an un-versioned executable.
pub fn executable_name ( & self ) -> String {
format! (
" python {var} {exe} " ,
var = self . variant . suffix ( ) ,
exe = std ::env ::consts ::EXE_SUFFIX
)
}
2024-06-17 14:11:24 -04:00
}
2024-07-03 08:44:29 -04:00
impl fmt ::Display for PythonInstallationKey {
2024-06-17 14:11:24 -04:00
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
2024-10-14 15:18:52 -05:00
let variant = match self . variant {
PythonVariant ::Default = > String ::new ( ) ,
PythonVariant ::Freethreaded = > format! ( " + {} " , self . variant ) ,
} ;
2024-06-17 14:11:24 -04:00
write! (
f ,
2024-10-14 15:18:52 -05:00
" {}-{}.{}.{}{}{}-{}-{}-{} " ,
2024-09-10 14:36:16 -05:00
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 ( ) ,
2024-10-14 15:18:52 -05:00
variant ,
2024-09-10 14:36:16 -05:00
self . os ,
self . arch ,
self . libc
2024-06-17 14:11:24 -04:00
)
}
}
2024-07-03 08:44:29 -04:00
impl FromStr for PythonInstallationKey {
type Err = PythonInstallationKeyError ;
2024-06-17 14:11:24 -04:00
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 (
2024-06-17 14:11:24 -04:00
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} " ) )
2024-06-17 14:11:24 -04:00
} ) ? ;
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} " ) ,
)
2024-06-17 14:11:24 -04:00
} ) ? ;
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} " ) )
2024-06-17 14:11:24 -04:00
} ) ? ;
2024-10-14 15:18:52 -05:00
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 ) ,
} ;
2024-09-10 14:36:16 -05:00
let version = PythonVersion ::from_str ( version ) . map_err ( | err | {
PythonInstallationKeyError ::ParseError (
2024-06-17 14:11:24 -04:00
key . to_string ( ) ,
2024-09-10 14:36:16 -05:00
format! ( " invalid Python version: {err} " ) ,
)
} ) ? ;
2024-06-17 14:11:24 -04:00
2024-09-10 14:36:16 -05:00
Ok ( Self ::new_from_version (
2024-06-17 14:11:24 -04:00
implementation ,
2024-09-10 14:36:16 -05:00
& version ,
2024-06-17 14:11:24 -04:00
os ,
arch ,
libc ,
2024-10-14 15:18:52 -05:00
variant ,
2024-06-17 14:11:24 -04:00
) )
}
}
2024-07-03 08:44:29 -04:00
impl PartialOrd for PythonInstallationKey {
2024-06-17 14:11:24 -04:00
fn partial_cmp ( & self , other : & Self ) -> Option < std ::cmp ::Ordering > {
Some ( self . cmp ( other ) )
}
}
2024-07-17 22:48:04 +08:00
2024-07-03 08:44:29 -04:00
impl Ord for PythonInstallationKey {
2024-06-17 14:11:24 -04:00
fn cmp ( & self , other : & Self ) -> std ::cmp ::Ordering {
2024-07-17 22:48:04 +08:00
self . implementation
. cmp ( & other . implementation )
2024-09-10 14:36:16 -05:00
. then_with ( | | self . version ( ) . cmp ( & other . version ( ) ) )
2024-07-17 22:48:04 +08:00
. then_with ( | | self . os . to_string ( ) . cmp ( & other . os . to_string ( ) ) )
2025-05-29 14:06:33 -05:00
// Architectures are sorted in preferred order, with native architectures first
. then_with ( | | self . arch . cmp ( & other . arch ) . reverse ( ) )
2024-07-17 22:48:04 +08:00
. then_with ( | | self . libc . to_string ( ) . cmp ( & other . libc . to_string ( ) ) )
2025-05-29 14:06:33 -05:00
// Python variants are sorted in preferred order, with `Default` first
. then_with ( | | self . variant . cmp ( & other . variant ) . reverse ( ) )
2024-06-17 14:11:24 -04:00
}
}
2025-06-20 10:17:13 -04:00
/// A view into a [`PythonInstallationKey`] that excludes the patch and prerelease versions.
#[ derive(Clone, Eq, Ord, PartialOrd, RefCast) ]
#[ repr(transparent) ]
pub struct PythonInstallationMinorVersionKey ( PythonInstallationKey ) ;
impl PythonInstallationMinorVersionKey {
/// Cast a `&PythonInstallationKey` to a `&PythonInstallationMinorVersionKey` using ref-cast.
#[ inline ]
pub fn ref_cast ( key : & PythonInstallationKey ) -> & Self {
RefCast ::ref_cast ( key )
}
/// Takes an [`IntoIterator`] of [`ManagedPythonInstallation`]s and returns an [`FxHashMap`] from
/// [`PythonInstallationMinorVersionKey`] to the installation with highest [`PythonInstallationKey`]
/// for that minor version key.
#[ inline ]
pub fn highest_installations_by_minor_version_key < ' a , I > (
installations : I ,
) -> IndexMap < Self , ManagedPythonInstallation >
where
I : IntoIterator < Item = & ' a ManagedPythonInstallation > ,
{
let mut minor_versions = IndexMap ::default ( ) ;
for installation in installations {
minor_versions
. entry ( installation . minor_version_key ( ) . clone ( ) )
. and_modify ( | high_installation : & mut ManagedPythonInstallation | {
if installation . key ( ) > = high_installation . key ( ) {
* high_installation = installation . clone ( ) ;
}
} )
. or_insert_with ( | | installation . clone ( ) ) ;
}
minor_versions
}
}
impl fmt ::Display for PythonInstallationMinorVersionKey {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
// Display every field on the wrapped key except the patch
// and prerelease (with special formatting for the variant).
let variant = match self . 0. variant {
PythonVariant ::Default = > String ::new ( ) ,
PythonVariant ::Freethreaded = > format! ( " + {} " , self . 0. variant ) ,
} ;
write! (
f ,
" {}-{}.{}{}-{}-{}-{} " ,
self . 0. implementation ,
self . 0. major ,
self . 0. minor ,
variant ,
self . 0. os ,
self . 0. arch ,
self . 0. libc ,
)
}
}
impl fmt ::Debug for PythonInstallationMinorVersionKey {
fn fmt ( & self , f : & mut fmt ::Formatter < '_ > ) -> fmt ::Result {
// Display every field on the wrapped key except the patch
// and prerelease.
f . debug_struct ( " PythonInstallationMinorVersionKey " )
. field ( " implementation " , & self . 0. implementation )
. field ( " major " , & self . 0. major )
. field ( " minor " , & self . 0. minor )
. field ( " variant " , & self . 0. variant )
. field ( " os " , & self . 0. os )
. field ( " arch " , & self . 0. arch )
. field ( " libc " , & self . 0. libc )
. finish ( )
}
}
impl PartialEq for PythonInstallationMinorVersionKey {
fn eq ( & self , other : & Self ) -> bool {
// Compare every field on the wrapped key except the patch
// and prerelease.
self . 0. implementation = = other . 0. implementation
& & self . 0. major = = other . 0. major
& & self . 0. minor = = other . 0. minor
& & self . 0. os = = other . 0. os
& & self . 0. arch = = other . 0. arch
& & self . 0. libc = = other . 0. libc
& & self . 0. variant = = other . 0. variant
}
}
impl Hash for PythonInstallationMinorVersionKey {
fn hash < H : Hasher > ( & self , state : & mut H ) {
// Hash every field on the wrapped key except the patch
// and prerelease.
self . 0. implementation . hash ( state ) ;
self . 0. major . hash ( state ) ;
self . 0. minor . hash ( state ) ;
self . 0. os . hash ( state ) ;
self . 0. arch . hash ( state ) ;
self . 0. libc . hash ( state ) ;
self . 0. variant . hash ( state ) ;
}
}
impl From < PythonInstallationKey > for PythonInstallationMinorVersionKey {
fn from ( key : PythonInstallationKey ) -> Self {
PythonInstallationMinorVersionKey ( key )
}
}