2024-07-07 20:23:59 -05:00
use std ::borrow ::Cow ;
2024-12-12 13:21:35 -05:00
use std ::env ::consts ::ARCH ;
2025-01-21 22:45:29 +01:00
use std ::fmt ::{ Display , Formatter } ;
2025-02-19 17:10:21 +01:00
use std ::io ;
2023-10-25 22:11:36 +02:00
use std ::path ::{ Path , PathBuf } ;
2024-05-21 15:37:23 -04:00
use std ::process ::{ Command , ExitStatus } ;
2024-07-26 21:49:47 -04:00
use std ::sync ::OnceLock ;
2023-10-25 22:11:36 +02:00
2024-02-27 23:18:45 -05:00
use configparser ::ini ::Ini ;
2023-11-28 18:14:59 +01:00
use fs_err as fs ;
2025-01-21 22:45:29 +01:00
use owo_colors ::OwoColorize ;
2024-06-06 16:15:28 -04:00
use same_file ::is_same_file ;
2023-10-25 22:11:36 +02:00
use serde ::{ Deserialize , Serialize } ;
2024-05-21 15:37:23 -04:00
use thiserror ::Error ;
2025-02-04 17:23:06 -05:00
use tracing ::{ debug , trace , warn } ;
2023-10-25 22:11:36 +02:00
2025-02-19 17:10:21 +01:00
use uv_cache ::{ Cache , CacheBucket , CachedByTimestamp , Freshness } ;
2024-09-09 16:19:15 -04:00
use uv_cache_info ::Timestamp ;
2024-10-01 20:15:32 -04:00
use uv_cache_key ::cache_digest ;
2024-03-19 20:19:46 -04:00
use uv_fs ::{ write_atomic_sync , PythonExt , Simplified } ;
2024-10-01 20:45:39 -04:00
use uv_install_wheel ::Layout ;
2024-10-01 20:15:32 -04:00
use uv_pep440 ::Version ;
use uv_pep508 ::{ MarkerEnvironment , StringVersion } ;
use uv_platform_tags ::Platform ;
use uv_platform_tags ::{ Tags , TagsError } ;
use uv_pypi_types ::{ ResolverMarkerEnvironment , Scheme } ;
2023-10-25 22:11:36 +02:00
2024-07-23 16:29:15 -04:00
use crate ::implementation ::LenientImplementationName ;
use crate ::platform ::{ Arch , Libc , Os } ;
2024-05-14 14:33:44 -04:00
use crate ::pointer_size ::PointerSize ;
2024-09-24 12:52:15 -05:00
use crate ::{
2024-10-14 15:18:52 -05:00
Prefix , PythonInstallationKey , PythonVariant , PythonVersion , Target , VersionRequest ,
VirtualEnvironment ,
2024-09-24 12:52:15 -05:00
} ;
2023-11-02 10:40:20 -07:00
2023-10-25 22:11:36 +02:00
/// A Python executable and its associated platform markers.
#[ derive(Debug, Clone) ]
2023-11-23 09:57:33 +01:00
pub struct Interpreter {
2024-02-28 10:04:55 -05:00
platform : Platform ,
2024-02-27 21:10:29 -05:00
markers : Box < MarkerEnvironment > ,
2024-03-04 12:50:13 -08:00
scheme : Scheme ,
2024-03-05 13:13:24 -08:00
virtualenv : Scheme ,
2024-08-20 21:57:42 -04:00
manylinux_compatible : bool ,
2024-06-05 22:11:21 -04:00
sys_prefix : PathBuf ,
sys_base_exec_prefix : PathBuf ,
sys_base_prefix : PathBuf ,
sys_base_executable : Option < PathBuf > ,
2024-02-27 21:10:29 -05:00
sys_executable : PathBuf ,
2024-05-10 02:41:32 -04:00
sys_path : Vec < PathBuf > ,
2024-03-05 13:13:24 -08:00
stdlib : PathBuf ,
2024-12-13 14:36:22 -05:00
standalone : bool ,
2024-07-26 21:49:47 -04:00
tags : OnceLock < Tags > ,
2024-04-25 19:15:39 -04:00
target : Option < Target > ,
2024-06-06 16:15:28 -04:00
prefix : Option < Prefix > ,
2024-05-14 14:33:44 -04:00
pointer_size : PointerSize ,
2024-04-12 11:39:47 +02:00
gil_disabled : bool ,
2023-10-25 22:11:36 +02:00
}
2023-11-23 09:57:33 +01:00
impl Interpreter {
2023-11-02 10:40:20 -07:00
/// Detect the interpreter info for the given Python executable.
2024-03-13 12:51:14 +01:00
pub fn query ( executable : impl AsRef < Path > , cache : & Cache ) -> Result < Self , Error > {
2024-03-10 14:44:50 +01:00
let info = InterpreterInfo ::query_cached ( executable . as_ref ( ) , cache ) ? ;
2024-02-22 08:47:33 +01:00
2024-01-05 16:01:06 +01:00
debug_assert! (
info . sys_executable . is_absolute ( ) ,
2024-01-30 10:52:46 +01:00
" `sys.executable` is not an absolute Python; Python installation is broken: {} " ,
2024-01-05 16:01:06 +01:00
info . sys_executable . display ( )
) ;
2023-10-25 22:11:36 +02:00
Ok ( Self {
2024-03-13 12:51:14 +01:00
platform : info . platform ,
2024-02-06 23:08:18 +01:00
markers : Box ::new ( info . markers ) ,
2024-03-04 12:50:13 -08:00
scheme : info . scheme ,
2024-03-05 13:13:24 -08:00
virtualenv : info . virtualenv ,
2024-08-20 21:57:42 -04:00
manylinux_compatible : info . manylinux_compatible ,
2024-06-05 22:11:21 -04:00
sys_prefix : info . sys_prefix ,
sys_base_exec_prefix : info . sys_base_exec_prefix ,
2024-05-14 14:33:44 -04:00
pointer_size : info . pointer_size ,
2024-04-12 11:39:47 +02:00
gil_disabled : info . gil_disabled ,
2024-06-05 22:11:21 -04:00
sys_base_prefix : info . sys_base_prefix ,
sys_base_executable : info . sys_base_executable ,
2023-11-16 12:16:49 +01:00
sys_executable : info . sys_executable ,
2024-05-10 02:41:32 -04:00
sys_path : info . sys_path ,
2024-03-05 13:13:24 -08:00
stdlib : info . stdlib ,
2024-12-13 14:36:22 -05:00
standalone : info . standalone ,
2024-07-26 21:49:47 -04:00
tags : OnceLock ::new ( ) ,
2024-04-25 19:15:39 -04:00
target : None ,
2024-06-06 16:15:28 -04:00
prefix : None ,
2023-10-25 22:11:36 +02:00
} )
}
2023-11-13 17:14:07 +01:00
2024-02-27 21:10:29 -05:00
/// Return a new [`Interpreter`] with the given virtual environment root.
2023-12-25 08:41:10 -05:00
#[ must_use ]
2024-05-02 06:58:48 -05:00
pub fn with_virtualenv ( self , virtualenv : VirtualEnvironment ) -> Self {
2023-12-25 08:41:10 -05:00
Self {
2024-03-04 12:50:13 -08:00
scheme : virtualenv . scheme ,
2025-02-04 17:32:47 -05:00
sys_base_executable : Some ( virtualenv . base_executable ) ,
2024-03-01 10:52:48 -05:00
sys_executable : virtualenv . executable ,
2024-06-05 22:11:21 -04:00
sys_prefix : virtualenv . root ,
2024-05-03 19:21:23 -04:00
target : None ,
2024-06-06 16:15:28 -04:00
prefix : None ,
2023-12-25 08:41:10 -05:00
.. self
2023-11-13 17:14:07 +01:00
}
}
2023-10-25 22:11:36 +02:00
2024-04-25 19:15:39 -04:00
/// Return a new [`Interpreter`] to install into the given `--target` directory.
2024-07-08 09:15:25 -05:00
pub fn with_target ( self , target : Target ) -> io ::Result < Self > {
target . init ( ) ? ;
Ok ( Self {
2024-04-25 19:15:39 -04:00
target : Some ( target ) ,
.. self
2024-07-08 09:15:25 -05:00
} )
2024-04-25 19:15:39 -04:00
}
2024-06-06 16:15:28 -04:00
/// Return a new [`Interpreter`] to install into the given `--prefix` directory.
2024-07-08 09:15:25 -05:00
pub fn with_prefix ( self , prefix : Prefix ) -> io ::Result < Self > {
prefix . init ( self . virtualenv ( ) ) ? ;
Ok ( Self {
2024-06-06 16:15:28 -04:00
prefix : Some ( prefix ) ,
.. self
2024-07-08 09:15:25 -05:00
} )
2024-06-06 16:15:28 -04:00
}
2025-02-05 15:47:56 -05:00
/// Return the base Python executable; that is, the Python executable that should be
2025-02-04 17:23:06 -05:00
/// considered the "base" for the virtual environment. This is typically the Python executable
/// from the [`Interpreter`]; however, if the interpreter is a virtual environment itself, then
/// the base Python executable is the Python executable of the interpreter's base interpreter.
2025-02-05 15:47:56 -05:00
///
/// This routine relies on `sys._base_executable`, falling back to `sys.executable` if unset.
/// Broadly, this routine should be used when attempting to determine the "base Python
/// executable" in a way that is consistent with the CPython standard library, such as when
/// determining the `home` key for a virtual environment.
2025-02-04 17:23:06 -05:00
pub fn to_base_python ( & self ) -> Result < PathBuf , io ::Error > {
let base_executable = self . sys_base_executable ( ) . unwrap_or ( self . sys_executable ( ) ) ;
2025-02-05 15:47:56 -05:00
let base_python = std ::path ::absolute ( base_executable ) ? ;
Ok ( base_python )
}
/// Determine the base Python executable; that is, the Python executable that should be
/// considered the "base" for the virtual environment. This is typically the Python executable
/// from the [`Interpreter`]; however, if the interpreter is a virtual environment itself, then
/// the base Python executable is the Python executable of the interpreter's base interpreter.
///
/// This routine mimics the CPython `getpath.py` logic in order to make a more robust assessment
/// of the appropriate base Python executable. Broadly, this routine should be used when
/// attempting to determine the "true" base executable for a Python interpreter by resolving
/// symlinks until a valid Python installation is found. In particular, we tend to use this
/// routine for our own managed (or standalone) Python installations.
pub fn find_base_python ( & self ) -> Result < PathBuf , io ::Error > {
let base_executable = self . sys_base_executable ( ) . unwrap_or ( self . sys_executable ( ) ) ;
// In `python-build-standalone`, a symlinked interpreter will return its own executable path
// as `sys._base_executable`. Using the symlinked path as the base Python executable can be
// incorrect, since it could cause `home` to point to something that is _not_ a Python
// installation. Specifically, if the interpreter _itself_ is symlinked to an arbitrary
// location, we need to fully resolve it to the actual Python executable; however, if the
// entire standalone interpreter is symlinked, then we can use the symlinked path.
//
// We emulate CPython's `getpath.py` to ensure that the base executable results in a valid
// Python prefix when converted into the `home` key for `pyvenv.cfg`.
let base_python = match find_base_python (
base_executable ,
self . python_major ( ) ,
self . python_minor ( ) ,
self . variant ( ) . suffix ( ) ,
) {
Ok ( path ) = > path ,
Err ( err ) = > {
warn! ( " Failed to find base Python executable: {err} " ) ;
uv_fs ::canonicalize_executable ( base_executable ) ?
2024-07-04 13:38:53 -04:00
}
2025-02-04 17:23:06 -05:00
} ;
Ok ( base_python )
2024-07-04 13:38:53 -04:00
}
2023-10-25 22:11:36 +02:00
/// Returns the path to the Python virtual environment.
2024-01-14 10:39:15 -05:00
#[ inline ]
2023-10-25 22:11:36 +02:00
pub fn platform ( & self ) -> & Platform {
& self . platform
}
/// Returns the [`MarkerEnvironment`] for this Python executable.
2024-01-14 10:39:15 -05:00
#[ inline ]
pub const fn markers ( & self ) -> & MarkerEnvironment {
2023-10-25 22:11:36 +02:00
& self . markers
}
2024-08-26 14:00:21 -04:00
/// Return the [`ResolverMarkerEnvironment`] for this Python executable.
2024-10-11 15:23:38 -04:00
pub fn resolver_marker_environment ( & self ) -> ResolverMarkerEnvironment {
2024-08-26 14:00:21 -04:00
ResolverMarkerEnvironment ::from ( self . markers ( ) . clone ( ) )
}
2024-07-23 16:29:15 -04:00
/// Returns the [`PythonInstallationKey`] for this interpreter.
pub fn key ( & self ) -> PythonInstallationKey {
PythonInstallationKey ::new (
LenientImplementationName ::from ( self . implementation_name ( ) ) ,
self . python_major ( ) ,
self . python_minor ( ) ,
self . python_patch ( ) ,
2024-10-08 23:20:58 +02:00
self . python_version ( ) . pre ( ) ,
2024-07-23 16:29:15 -04:00
self . os ( ) ,
self . arch ( ) ,
self . libc ( ) ,
2024-10-14 15:18:52 -05:00
self . variant ( ) ,
2024-07-23 16:29:15 -04:00
)
}
2024-10-14 15:18:52 -05:00
pub fn variant ( & self ) -> PythonVariant {
if self . gil_disabled ( ) {
PythonVariant ::Freethreaded
} else {
PythonVariant ::default ( )
}
}
2024-07-23 16:29:15 -04:00
/// Return the [`Arch`] reported by the interpreter platform tags.
pub fn arch ( & self ) -> Arch {
Arch ::from ( & self . platform ( ) . arch ( ) )
}
/// Return the [`Libc`] reported by the interpreter platform tags.
pub fn libc ( & self ) -> Libc {
Libc ::from ( self . platform ( ) . os ( ) )
}
/// Return the [`Os`] reported by the interpreter platform tags.
pub fn os ( & self ) -> Os {
Os ::from ( self . platform ( ) . os ( ) )
}
2023-12-24 18:06:50 -05:00
/// Returns the [`Tags`] for this Python executable.
2024-01-22 09:22:27 -05:00
pub fn tags ( & self ) -> Result < & Tags , TagsError > {
2024-07-26 21:49:47 -04:00
if self . tags . get ( ) . is_none ( ) {
let tags = Tags ::from_env (
2024-01-22 09:22:27 -05:00
self . platform ( ) ,
self . python_tuple ( ) ,
self . implementation_name ( ) ,
self . implementation_tuple ( ) ,
2024-08-20 21:57:42 -04:00
self . manylinux_compatible ,
2024-04-12 11:39:47 +02:00
self . gil_disabled ,
2024-07-26 21:49:47 -04:00
) ? ;
self . tags . set ( tags ) . expect ( " tags should not be set " ) ;
}
Ok ( self . tags . get ( ) . expect ( " tags should be set " ) )
2023-12-24 18:06:50 -05:00
}
2024-02-27 21:10:29 -05:00
/// Returns `true` if the environment is a PEP 405-compliant virtual environment.
///
/// See: <https://github.com/pypa/pip/blob/0ad4c94be74cc24874c6feb5bb3c2152c398a18e/src/pip/_internal/utils/virtualenv.py#L14>
pub fn is_virtualenv ( & self ) -> bool {
2024-04-25 19:15:39 -04:00
// Maybe this should return `false` if it's a target?
2024-06-05 22:11:21 -04:00
self . sys_prefix ! = self . sys_base_prefix
2024-02-27 21:10:29 -05:00
}
2024-04-25 19:15:39 -04:00
/// Returns `true` if the environment is a `--target` environment.
pub fn is_target ( & self ) -> bool {
self . target . is_some ( )
}
2024-06-06 16:15:28 -04:00
/// Returns `true` if the environment is a `--prefix` environment.
pub fn is_prefix ( & self ) -> bool {
self . prefix . is_some ( )
}
2024-02-27 23:18:45 -05:00
/// Returns `Some` if the environment is externally managed, optionally including an error
/// message from the `EXTERNALLY-MANAGED` file.
2024-02-27 21:10:29 -05:00
///
/// See: <https://packaging.python.org/en/latest/specifications/externally-managed-environments/>
2024-02-27 23:18:45 -05:00
pub fn is_externally_managed ( & self ) -> Option < ExternallyManaged > {
2024-02-27 21:10:29 -05:00
// Per the spec, a virtual environment is never externally managed.
if self . is_virtualenv ( ) {
2024-02-27 23:18:45 -05:00
return None ;
2024-02-27 21:10:29 -05:00
}
2024-06-06 16:15:28 -04:00
// If we're installing into a target or prefix directory, it's never externally managed.
if self . is_target ( ) | | self . is_prefix ( ) {
2024-04-25 19:15:39 -04:00
return None ;
}
2024-03-05 13:13:24 -08:00
let Ok ( contents ) = fs ::read_to_string ( self . stdlib . join ( " EXTERNALLY-MANAGED " ) ) else {
2024-02-27 23:18:45 -05:00
return None ;
} ;
2024-02-28 22:00:23 -05:00
let mut ini = Ini ::new_cs ( ) ;
ini . set_multiline ( true ) ;
let Ok ( mut sections ) = ini . read ( contents ) else {
2024-02-27 23:18:45 -05:00
// If a file exists but is not a valid INI file, we assume the environment is
// externally managed.
return Some ( ExternallyManaged ::default ( ) ) ;
} ;
2024-02-28 22:00:23 -05:00
let Some ( section ) = sections . get_mut ( " externally-managed " ) else {
2024-02-27 23:18:45 -05:00
// If the file exists but does not contain an "externally-managed" section, we assume
// the environment is externally managed.
return Some ( ExternallyManaged ::default ( ) ) ;
} ;
let Some ( error ) = section . remove ( " Error " ) else {
// If the file exists but does not contain an "Error" key, we assume the environment is
// externally managed.
return Some ( ExternallyManaged ::default ( ) ) ;
} ;
Some ( ExternallyManaged { error } )
2024-02-27 21:10:29 -05:00
}
2024-06-05 16:45:50 -04:00
/// Returns the `python_full_version` marker corresponding to this Python version.
#[ inline ]
pub fn python_full_version ( & self ) -> & StringVersion {
self . markers . python_full_version ( )
}
/// Returns the full Python version.
2024-01-14 10:39:15 -05:00
#[ inline ]
2024-05-08 19:01:44 -04:00
pub fn python_version ( & self ) -> & Version {
& self . markers . python_full_version ( ) . version
2023-10-25 22:11:36 +02:00
}
2024-06-05 16:45:50 -04:00
/// Returns the full minor Python version.
2024-03-12 17:11:50 -07:00
#[ inline ]
2024-06-05 16:45:50 -04:00
pub fn python_minor_version ( & self ) -> Version {
Version ::new ( self . python_version ( ) . release ( ) . iter ( ) . take ( 2 ) . copied ( ) )
2024-03-12 17:11:50 -07:00
}
2024-01-14 10:39:15 -05:00
/// Return the major version of this Python version.
2024-01-22 09:22:27 -05:00
pub fn python_major ( & self ) -> u8 {
2024-05-08 19:01:44 -04:00
let major = self . markers . python_full_version ( ) . version . release ( ) [ 0 ] ;
2024-01-14 10:39:15 -05:00
u8 ::try_from ( major ) . expect ( " invalid major version " )
}
/// Return the minor version of this Python version.
2024-01-22 09:22:27 -05:00
pub fn python_minor ( & self ) -> u8 {
2024-05-08 19:01:44 -04:00
let minor = self . markers . python_full_version ( ) . version . release ( ) [ 1 ] ;
2024-01-14 10:39:15 -05:00
u8 ::try_from ( minor ) . expect ( " invalid minor version " )
}
2024-02-08 16:38:00 -05:00
/// Return the patch version of this Python version.
pub fn python_patch ( & self ) -> u8 {
2024-05-08 19:01:44 -04:00
let minor = self . markers . python_full_version ( ) . version . release ( ) [ 2 ] ;
2024-02-08 16:38:00 -05:00
u8 ::try_from ( minor ) . expect ( " invalid patch version " )
}
2023-10-25 22:11:36 +02:00
/// Returns the Python version as a simple tuple.
2024-01-22 09:22:27 -05:00
pub fn python_tuple ( & self ) -> ( u8 , u8 ) {
( self . python_major ( ) , self . python_minor ( ) )
}
/// Return the major version of the implementation (e.g., `CPython` or `PyPy`).
pub fn implementation_major ( & self ) -> u8 {
2024-05-08 19:01:44 -04:00
let major = self . markers . implementation_version ( ) . version . release ( ) [ 0 ] ;
2024-01-22 09:22:27 -05:00
u8 ::try_from ( major ) . expect ( " invalid major version " )
}
/// Return the minor version of the implementation (e.g., `CPython` or `PyPy`).
pub fn implementation_minor ( & self ) -> u8 {
2024-05-08 19:01:44 -04:00
let minor = self . markers . implementation_version ( ) . version . release ( ) [ 1 ] ;
2024-01-22 09:22:27 -05:00
u8 ::try_from ( minor ) . expect ( " invalid minor version " )
2023-10-25 22:11:36 +02:00
}
2024-01-22 09:22:27 -05:00
/// Returns the implementation version as a simple tuple.
pub fn implementation_tuple ( & self ) -> ( u8 , u8 ) {
( self . implementation_major ( ) , self . implementation_minor ( ) )
}
2024-02-27 21:10:29 -05:00
/// Returns the implementation name (e.g., `CPython` or `PyPy`).
2024-01-22 09:22:27 -05:00
pub fn implementation_name ( & self ) -> & str {
2024-05-08 19:01:44 -04:00
self . markers . implementation_name ( )
2024-01-22 09:22:27 -05:00
}
2024-02-27 21:10:29 -05:00
/// Return the `sys.base_exec_prefix` path for this Python interpreter.
2024-06-05 22:11:21 -04:00
pub fn sys_base_exec_prefix ( & self ) -> & Path {
& self . sys_base_exec_prefix
2023-10-25 22:11:36 +02:00
}
2024-02-27 21:10:29 -05:00
/// Return the `sys.base_prefix` path for this Python interpreter.
2024-06-05 22:11:21 -04:00
pub fn sys_base_prefix ( & self ) -> & Path {
& self . sys_base_prefix
2023-10-25 22:11:36 +02:00
}
2024-01-24 18:27:49 +01:00
2024-03-01 10:52:48 -05:00
/// Return the `sys.prefix` path for this Python interpreter.
2024-06-05 22:11:21 -04:00
pub fn sys_prefix ( & self ) -> & Path {
& self . sys_prefix
2024-03-01 10:52:48 -05:00
}
2024-03-03 09:44:10 -08:00
/// Return the `sys._base_executable` path for this Python interpreter. Some platforms do not
/// have this attribute, so it may be `None`.
2024-06-05 22:11:21 -04:00
pub fn sys_base_executable ( & self ) -> Option < & Path > {
self . sys_base_executable . as_deref ( )
2024-03-03 09:44:10 -08:00
}
2024-02-27 21:10:29 -05:00
/// Return the `sys.executable` path for this Python interpreter.
2023-11-16 12:16:49 +01:00
pub fn sys_executable ( & self ) -> & Path {
& self . sys_executable
}
2024-02-27 21:10:29 -05:00
2024-05-10 02:41:32 -04:00
/// Return the `sys.path` for this Python interpreter.
pub fn sys_path ( & self ) -> & Vec < PathBuf > {
& self . sys_path
}
2024-03-05 13:13:24 -08:00
/// Return the `stdlib` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn stdlib ( & self ) -> & Path {
& self . stdlib
}
2024-02-27 21:10:29 -05:00
/// Return the `purelib` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn purelib ( & self ) -> & Path {
2024-03-04 12:50:13 -08:00
& self . scheme . purelib
2024-02-27 21:10:29 -05:00
}
/// Return the `platlib` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn platlib ( & self ) -> & Path {
2024-03-04 12:50:13 -08:00
& self . scheme . platlib
2024-02-27 21:10:29 -05:00
}
/// Return the `scripts` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn scripts ( & self ) -> & Path {
2024-03-04 12:50:13 -08:00
& self . scheme . scripts
2024-02-27 21:10:29 -05:00
}
/// Return the `data` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn data ( & self ) -> & Path {
2024-03-04 12:50:13 -08:00
& self . scheme . data
2024-02-27 21:10:29 -05:00
}
/// Return the `include` path for this Python interpreter, as returned by `sysconfig.get_paths()`.
pub fn include ( & self ) -> & Path {
2024-03-04 12:50:13 -08:00
& self . scheme . include
2024-03-01 10:52:48 -05:00
}
2024-03-05 13:13:24 -08:00
/// Return the [`Scheme`] for a virtual environment created by this [`Interpreter`].
pub fn virtualenv ( & self ) -> & Scheme {
& self . virtualenv
2024-02-29 14:23:17 -05:00
}
2024-08-20 21:57:42 -04:00
/// Return whether this interpreter is `manylinux` compatible.
pub fn manylinux_compatible ( & self ) -> bool {
self . manylinux_compatible
}
2024-05-14 14:33:44 -04:00
/// Return the [`PointerSize`] of the Python interpreter (i.e., 32- vs. 64-bit).
pub fn pointer_size ( & self ) -> PointerSize {
self . pointer_size
}
2024-04-12 11:39:47 +02:00
/// Return whether this is a Python 3.13+ freethreading Python, as specified by the sysconfig var
/// `Py_GIL_DISABLED`.
///
/// freethreading Python is incompatible with earlier native modules, re-introducing
/// abiflags with a `t` flag. <https://peps.python.org/pep-0703/#build-configuration-changes>
pub fn gil_disabled ( & self ) -> bool {
self . gil_disabled
}
2024-04-25 19:15:39 -04:00
/// Return the `--target` directory for this interpreter, if any.
pub fn target ( & self ) -> Option < & Target > {
self . target . as_ref ( )
}
2024-06-06 16:15:28 -04:00
/// Return the `--prefix` directory for this interpreter, if any.
pub fn prefix ( & self ) -> Option < & Prefix > {
self . prefix . as_ref ( )
}
2024-10-28 12:06:25 -04:00
/// Returns `true` if an [`Interpreter`] may be a `python-build-standalone` interpreter.
///
/// This method may return false positives, but it should not return false negatives. In other
/// words, if this method returns `true`, the interpreter _may_ be from
/// `python-build-standalone`; if it returns `false`, the interpreter is definitely _not_ from
/// `python-build-standalone`.
///
2024-12-17 14:19:58 -06:00
/// See: <https://github.com/astral-sh/python-build-standalone/issues/382>
2024-10-28 12:06:25 -04:00
pub fn is_standalone ( & self ) -> bool {
2024-12-13 14:36:22 -05:00
self . standalone
2024-10-28 12:06:25 -04:00
}
2024-02-27 21:10:29 -05:00
/// Return the [`Layout`] environment used to install wheels into this interpreter.
pub fn layout ( & self ) -> Layout {
Layout {
python_version : self . python_tuple ( ) ,
sys_executable : self . sys_executable ( ) . to_path_buf ( ) ,
2024-05-08 19:01:44 -04:00
os_name : self . markers . os_name ( ) . to_string ( ) ,
2024-04-25 19:15:39 -04:00
scheme : if let Some ( target ) = self . target . as_ref ( ) {
target . scheme ( )
2024-06-06 16:15:28 -04:00
} else if let Some ( prefix ) = self . prefix . as_ref ( ) {
prefix . scheme ( & self . virtualenv )
2024-04-25 19:15:39 -04:00
} else {
Scheme {
purelib : self . purelib ( ) . to_path_buf ( ) ,
platlib : self . platlib ( ) . to_path_buf ( ) ,
scripts : self . scripts ( ) . to_path_buf ( ) ,
data : self . data ( ) . to_path_buf ( ) ,
include : if self . is_virtualenv ( ) {
// If the interpreter is a venv, then the `include` directory has a different structure.
// See: https://github.com/pypa/pip/blob/0ad4c94be74cc24874c6feb5bb3c2152c398a18e/src/pip/_internal/locations/_sysconfig.py#L172
2024-06-05 22:11:21 -04:00
self . sys_prefix . join ( " include " ) . join ( " site " ) . join ( format! (
2024-04-25 19:15:39 -04:00
" python {} . {} " ,
self . python_major ( ) ,
self . python_minor ( )
) )
} else {
self . include ( ) . to_path_buf ( )
} ,
}
2024-03-04 12:50:13 -08:00
} ,
2024-02-27 21:10:29 -05:00
}
}
2024-04-10 11:22:41 -05:00
2024-07-07 20:23:59 -05:00
/// Returns an iterator over the `site-packages` directories inside the environment.
///
/// In most cases, `purelib` and `platlib` will be the same, and so the iterator will contain
/// a single element; however, in some distributions, they may be different.
///
/// Some distributions also create symbolic links from `purelib` to `platlib`; in such cases, we
/// still deduplicate the entries, returning a single path.
pub fn site_packages ( & self ) -> impl Iterator < Item = Cow < Path > > {
let target = self . target ( ) . map ( Target ::site_packages ) ;
let prefix = self
. prefix ( )
. map ( | prefix | prefix . site_packages ( self . virtualenv ( ) ) ) ;
let interpreter = if target . is_none ( ) & & prefix . is_none ( ) {
let purelib = self . purelib ( ) ;
let platlib = self . platlib ( ) ;
Some ( std ::iter ::once ( purelib ) . chain (
if purelib = = platlib | | is_same_file ( purelib , platlib ) . unwrap_or ( false ) {
None
} else {
Some ( platlib )
} ,
) )
} else {
None
} ;
target
. into_iter ( )
. flatten ( )
. map ( Cow ::Borrowed )
. chain ( prefix . into_iter ( ) . flatten ( ) . map ( Cow ::Owned ) )
. chain ( interpreter . into_iter ( ) . flatten ( ) . map ( Cow ::Borrowed ) )
2024-06-06 16:15:28 -04:00
}
2024-04-10 11:22:41 -05:00
/// Check if the interpreter matches the given Python version.
///
/// If a patch version is present, we will require an exact match.
/// Otherwise, just the major and minor version numbers need to match.
pub fn satisfies ( & self , version : & PythonVersion ) -> bool {
if version . patch ( ) . is_some ( ) {
version . version ( ) = = self . python_version ( )
} else {
( version . major ( ) , version . minor ( ) ) = = self . python_tuple ( )
}
}
2024-09-24 12:52:15 -05:00
/// Whether or not this Python interpreter is from a default Python executable name, like
/// `python`, `python3`, or `python.exe`.
pub ( crate ) fn has_default_executable_name ( & self ) -> bool {
let Some ( file_name ) = self . sys_executable ( ) . file_name ( ) else {
return false ;
} ;
let Some ( name ) = file_name . to_str ( ) else {
return false ;
} ;
VersionRequest ::Default
. executable_names ( None )
. into_iter ( )
. any ( | default_name | name = = default_name . to_string ( ) )
}
2023-10-25 22:11:36 +02:00
}
2024-02-27 23:18:45 -05:00
/// The `EXTERNALLY-MANAGED` file in a Python installation.
///
/// See: <https://packaging.python.org/en/latest/specifications/externally-managed-environments/>
#[ derive(Debug, Default, Clone) ]
pub struct ExternallyManaged {
error : Option < String > ,
}
impl ExternallyManaged {
/// Return the `EXTERNALLY-MANAGED` error message, if any.
pub fn into_error ( self ) -> Option < String > {
self . error
}
}
2025-01-21 22:45:29 +01:00
#[ derive(Debug, Error) ]
pub struct UnexpectedResponseError {
#[ source ]
pub ( super ) err : serde_json ::Error ,
pub ( super ) stdout : String ,
pub ( super ) stderr : String ,
pub ( super ) path : PathBuf ,
}
impl Display for UnexpectedResponseError {
fn fmt ( & self , f : & mut Formatter < '_ > ) -> std ::fmt ::Result {
write! (
f ,
" Querying Python at `{}` returned an invalid response: {} " ,
self . path . display ( ) ,
self . err
) ? ;
let mut non_empty = false ;
if ! self . stdout . trim ( ) . is_empty ( ) {
write! ( f , " \n \n {} \n {} " , " [stdout] " . red ( ) , self . stdout ) ? ;
non_empty = true ;
}
if ! self . stderr . trim ( ) . is_empty ( ) {
write! ( f , " \n \n {} \n {} " , " [stderr] " . red ( ) , self . stderr ) ? ;
non_empty = true ;
}
if non_empty {
writeln! ( f ) ? ;
}
Ok ( ( ) )
}
}
#[ derive(Debug, Error) ]
pub struct StatusCodeError {
pub ( super ) code : ExitStatus ,
pub ( super ) stdout : String ,
pub ( super ) stderr : String ,
pub ( super ) path : PathBuf ,
}
impl Display for StatusCodeError {
fn fmt ( & self , f : & mut Formatter < '_ > ) -> std ::fmt ::Result {
write! (
f ,
" Querying Python at `{}` failed with exit status {} " ,
self . path . display ( ) ,
self . code
) ? ;
let mut non_empty = false ;
if ! self . stdout . trim ( ) . is_empty ( ) {
write! ( f , " \n \n {} \n {} " , " [stdout] " . red ( ) , self . stdout ) ? ;
non_empty = true ;
}
if ! self . stderr . trim ( ) . is_empty ( ) {
write! ( f , " \n \n {} \n {} " , " [stderr] " . red ( ) , self . stderr ) ? ;
non_empty = true ;
}
if non_empty {
writeln! ( f ) ? ;
}
Ok ( ( ) )
}
}
2024-05-21 15:37:23 -04:00
#[ derive(Debug, Error) ]
pub enum Error {
2024-06-10 18:26:34 -04:00
#[ error( " Failed to query Python interpreter " ) ]
2024-05-21 15:37:23 -04:00
Io ( #[ from ] io ::Error ) ,
2024-06-10 18:26:34 -04:00
#[ error( " Python interpreter not found at `{0}` " ) ]
NotFound ( PathBuf ) ,
2024-05-21 15:37:23 -04:00
#[ error( " Failed to query Python interpreter at `{path}` " ) ]
SpawnFailed {
path : PathBuf ,
#[ source ]
err : io ::Error ,
} ,
2025-01-21 22:45:29 +01:00
#[ error( " {0} " ) ]
UnexpectedResponse ( UnexpectedResponseError ) ,
#[ error( " {0} " ) ]
StatusCode ( StatusCodeError ) ,
2024-05-21 15:37:23 -04:00
#[ error( " Can't use Python at `{path}` " ) ]
QueryScript {
#[ source ]
err : InterpreterInfoError ,
path : PathBuf ,
} ,
#[ error( " Failed to write to cache " ) ]
Encode ( #[ from ] rmp_serde ::encode ::Error ) ,
}
2024-03-13 12:51:14 +01:00
#[ derive(Debug, Deserialize, Serialize) ]
#[ serde(tag = " result " , rename_all = " lowercase " ) ]
enum InterpreterInfoResult {
Error ( InterpreterInfoError ) ,
Success ( Box < InterpreterInfo > ) ,
}
#[ derive(Debug, Error, Deserialize, Serialize) ]
#[ serde(tag = " kind " , rename_all = " snake_case " ) ]
pub enum InterpreterInfoError {
#[ error( " Could not detect a glibc or a musl libc (while running on Linux) " ) ]
LibcNotFound ,
2025-02-10 22:49:16 +01:00
#[ error( " Broken Python installation, `platform.mac_ver()` returned an empty value, please reinstall Python " ) ]
BrokenMacVer ,
2024-12-17 15:33:45 +02:00
#[ error( " Unknown operating system: `{operating_system}` " ) ]
2024-03-13 12:51:14 +01:00
UnknownOperatingSystem { operating_system : String } ,
2024-05-06 02:12:36 -07:00
#[ error( " Python {python_version} is not supported. Please use Python 3.8 or newer. " ) ]
UnsupportedPythonVersion { python_version : String } ,
2024-05-21 15:37:23 -04:00
#[ error( " Python executable does not support `-I` flag. Please use Python 3.8 or newer. " ) ]
UnsupportedPython ,
2025-01-20 11:29:29 -06:00
#[ error( " Python installation is missing `distutils`, which is required for packaging on older Python versions. Your system may package it separately, e.g., as `python{python_major}-distutils` or `python{python_major}.{python_minor}-distutils`. " ) ]
MissingRequiredDistutils {
python_major : usize ,
python_minor : usize ,
} ,
2024-03-13 12:51:14 +01:00
}
2024-02-27 21:10:29 -05:00
#[ derive(Debug, Deserialize, Serialize, Clone) ]
struct InterpreterInfo {
2024-03-13 12:51:14 +01:00
platform : Platform ,
2024-02-27 21:10:29 -05:00
markers : MarkerEnvironment ,
2024-03-04 12:50:13 -08:00
scheme : Scheme ,
2024-03-05 13:13:24 -08:00
virtualenv : Scheme ,
2024-08-20 21:57:42 -04:00
manylinux_compatible : bool ,
2024-06-05 22:11:21 -04:00
sys_prefix : PathBuf ,
sys_base_exec_prefix : PathBuf ,
sys_base_prefix : PathBuf ,
sys_base_executable : Option < PathBuf > ,
2024-02-27 21:10:29 -05:00
sys_executable : PathBuf ,
2024-05-10 02:41:32 -04:00
sys_path : Vec < PathBuf > ,
2024-03-05 13:13:24 -08:00
stdlib : PathBuf ,
2024-12-13 14:36:22 -05:00
standalone : bool ,
2024-05-14 14:33:44 -04:00
pointer_size : PointerSize ,
2024-04-12 11:39:47 +02:00
gil_disabled : bool ,
2023-10-25 22:11:36 +02:00
}
2024-02-23 11:55:38 +01:00
impl InterpreterInfo {
/// Return the resolved [`InterpreterInfo`] for the given Python executable.
2024-03-13 12:51:14 +01:00
pub ( crate ) fn query ( interpreter : & Path , cache : & Cache ) -> Result < Self , Error > {
let tempdir = tempfile ::tempdir_in ( cache . root ( ) ) ? ;
Self ::setup_python_query_files ( tempdir . path ( ) ) ? ;
2024-03-19 20:19:46 -04:00
// Sanitize the path by (1) running under isolated mode (`-I`) to ignore any site packages
// modifications, and then (2) adding the path containing our query script to the front of
// `sys.path` so that we can import it.
let script = format! (
r # "import sys; sys.path = ["{}"] + sys.path; from python.get_interpreter_info import main; main()"# ,
tempdir . path ( ) . escape_for_python ( )
) ;
2024-03-13 12:51:14 +01:00
let output = Command ::new ( interpreter )
2024-09-26 16:52:10 +03:00
. arg ( " -I " ) // Isolated mode.
. arg ( " -B " ) // Don't write bytecode.
2024-03-19 20:19:46 -04:00
. arg ( " -c " )
. arg ( script )
2024-03-13 12:51:14 +01:00
. output ( )
2024-05-21 15:37:23 -04:00
. map_err ( | err | Error ::SpawnFailed {
path : interpreter . to_path_buf ( ) ,
2024-03-13 12:51:14 +01:00
err ,
} ) ? ;
2023-10-25 22:11:36 +02:00
2024-03-21 19:23:41 -04:00
if ! output . status . success ( ) {
2024-05-21 15:37:23 -04:00
let stderr = String ::from_utf8_lossy ( & output . stderr ) . trim ( ) . to_string ( ) ;
// If the Python version is too old, we may not even be able to invoke the query script
if stderr . contains ( " Unknown option: -I " ) {
return Err ( Error ::QueryScript {
err : InterpreterInfoError ::UnsupportedPython ,
path : interpreter . to_path_buf ( ) ,
} ) ;
}
2025-01-21 22:45:29 +01:00
return Err ( Error ::StatusCode ( StatusCodeError {
2024-05-21 15:37:23 -04:00
code : output . status ,
stderr ,
2023-11-23 09:57:33 +01:00
stdout : String ::from_utf8_lossy ( & output . stdout ) . trim ( ) . to_string ( ) ,
2024-05-21 15:37:23 -04:00
path : interpreter . to_path_buf ( ) ,
2025-01-21 22:45:29 +01:00
} ) ) ;
2023-10-25 22:11:36 +02:00
}
2024-02-23 11:55:38 +01:00
2024-03-13 12:51:14 +01:00
let result : InterpreterInfoResult =
serde_json ::from_slice ( & output . stdout ) . map_err ( | err | {
2024-05-21 15:37:23 -04:00
let stderr = String ::from_utf8_lossy ( & output . stderr ) . trim ( ) . to_string ( ) ;
// If the Python version is too old, we may not even be able to invoke the query script
if stderr . contains ( " Unknown option: -I " ) {
Error ::QueryScript {
err : InterpreterInfoError ::UnsupportedPython ,
path : interpreter . to_path_buf ( ) ,
}
} else {
2025-01-21 22:45:29 +01:00
Error ::UnexpectedResponse ( UnexpectedResponseError {
2024-05-21 15:37:23 -04:00
err ,
stdout : String ::from_utf8_lossy ( & output . stdout ) . trim ( ) . to_string ( ) ,
stderr ,
path : interpreter . to_path_buf ( ) ,
2025-01-21 22:45:29 +01:00
} )
2024-03-13 12:51:14 +01:00
}
} ) ? ;
match result {
InterpreterInfoResult ::Error ( err ) = > Err ( Error ::QueryScript {
err ,
2024-05-21 15:37:23 -04:00
path : interpreter . to_path_buf ( ) ,
2024-03-13 12:51:14 +01:00
} ) ,
InterpreterInfoResult ::Success ( data ) = > Ok ( * data ) ,
}
}
2023-10-25 22:11:36 +02:00
2024-03-13 12:51:14 +01:00
/// Duplicate the directory structure we have in `../python` into a tempdir, so we can run
/// the Python probing scripts with `python -m python.get_interpreter_info` from that tempdir.
fn setup_python_query_files ( root : & Path ) -> Result < ( ) , Error > {
let python_dir = root . join ( " python " ) ;
fs_err ::create_dir ( & python_dir ) ? ;
fs_err ::write (
python_dir . join ( " get_interpreter_info.py " ) ,
include_str! ( " ../python/get_interpreter_info.py " ) ,
) ? ;
fs_err ::write (
python_dir . join ( " __init__.py " ) ,
include_str! ( " ../python/__init__.py " ) ,
) ? ;
let packaging_dir = python_dir . join ( " packaging " ) ;
fs_err ::create_dir ( & packaging_dir ) ? ;
fs_err ::write (
packaging_dir . join ( " __init__.py " ) ,
include_str! ( " ../python/packaging/__init__.py " ) ,
) ? ;
fs_err ::write (
packaging_dir . join ( " _elffile.py " ) ,
include_str! ( " ../python/packaging/_elffile.py " ) ,
) ? ;
fs_err ::write (
packaging_dir . join ( " _manylinux.py " ) ,
include_str! ( " ../python/packaging/_manylinux.py " ) ,
) ? ;
fs_err ::write (
packaging_dir . join ( " _musllinux.py " ) ,
include_str! ( " ../python/packaging/_musllinux.py " ) ,
) ? ;
Ok ( ( ) )
2023-10-25 22:11:36 +02:00
}
/// A wrapper around [`markers::query_interpreter_info`] to cache the computed markers.
///
/// Running a Python script is (relatively) expensive, and the markers won't change
/// unless the Python executable changes, so we use the executable's last modified
/// time as a cache key.
2023-11-28 18:14:59 +01:00
pub ( crate ) fn query_cached ( executable : & Path , cache : & Cache ) -> Result < Self , Error > {
2024-08-25 08:01:07 -04:00
let absolute = std ::path ::absolute ( executable ) ? ;
2024-07-26 08:57:33 -04:00
2023-12-06 17:47:01 +01:00
let cache_entry = cache . entry (
CacheBucket ::Interpreter ,
2025-03-01 20:36:38 -05:00
// Shard interpreter metadata by host architecture, operating system, and version, to
// invalidate the cache (e.g.) on OS upgrades.
cache_digest ( & (
ARCH ,
sys_info ::os_type ( ) . unwrap_or_default ( ) ,
sys_info ::os_release ( ) . unwrap_or_default ( ) ,
) ) ,
2024-07-26 08:57:33 -04:00
// We use the absolute path for the cache entry to avoid cache collisions for relative
// paths. But we don't to query the executable with symbolic links resolved.
2024-07-26 19:24:09 -04:00
format! ( " {} .msgpack " , cache_digest ( & absolute ) ) ,
2023-12-06 17:47:01 +01:00
) ;
2023-11-28 18:14:59 +01:00
2024-05-24 12:18:49 -04:00
// We check the timestamp of the canonicalized executable to check if an underlying
2024-07-26 08:57:33 -04:00
// interpreter has been modified.
let modified = uv_fs ::canonicalize_executable ( & absolute )
. and_then ( Timestamp ::from_path )
. map_err ( | err | {
2024-06-10 18:26:34 -04:00
if err . kind ( ) = = io ::ErrorKind ::NotFound {
Error ::NotFound ( executable . to_path_buf ( ) )
} else {
err . into ( )
}
2024-07-26 08:57:33 -04:00
} ) ? ;
2023-12-03 17:44:43 -05:00
2023-10-25 22:11:36 +02:00
// Read from the cache.
2025-02-19 17:10:21 +01:00
if cache
. freshness ( & cache_entry , None )
. is_ok_and ( Freshness ::is_fresh )
2024-01-23 18:30:26 -05:00
{
2025-02-19 17:10:21 +01:00
if let Ok ( data ) = fs ::read ( cache_entry . path ( ) ) {
match rmp_serde ::from_slice ::< CachedByTimestamp < Self > > ( & data ) {
Ok ( cached ) = > {
if cached . timestamp = = modified {
trace! (
" Cached interpreter info for Python {}, skipping probing: {} " ,
cached . data . markers . python_full_version ( ) ,
executable . user_display ( )
) ;
return Ok ( cached . data ) ;
}
trace! (
" Ignoring stale interpreter markers for: {} " ,
executable . user_display ( )
) ;
}
Err ( err ) = > {
warn! (
" Broken interpreter cache entry at {}, removing: {err} " ,
cache_entry . path ( ) . user_display ( )
) ;
let _ = fs_err ::remove_file ( cache_entry . path ( ) ) ;
}
}
}
2023-11-28 18:14:59 +01:00
}
2023-10-25 22:11:36 +02:00
// Otherwise, run the Python script.
2024-05-21 15:37:23 -04:00
trace! (
" Querying interpreter executable at {} " ,
executable . display ( )
) ;
2024-03-13 12:51:14 +01:00
let info = Self ::query ( executable , cache ) ? ;
2023-10-25 22:11:36 +02:00
2023-11-28 18:14:59 +01:00
// If `executable` is a pyenv shim, a bash script that redirects to the activated
2024-01-23 14:52:20 -05:00
// python executable at another path, we're not allowed to cache the interpreter info.
2024-07-03 19:25:39 -04:00
if is_same_file ( executable , & info . sys_executable ) . unwrap_or ( false ) {
2023-12-25 21:10:30 -05:00
fs ::create_dir_all ( cache_entry . dir ( ) ) ? ;
2023-12-04 18:02:01 +01:00
write_atomic_sync (
2023-12-06 17:47:01 +01:00
cache_entry . path ( ) ,
2025-02-19 17:10:21 +01:00
rmp_serde ::to_vec ( & CachedByTimestamp {
2023-12-06 17:47:01 +01:00
timestamp : modified ,
2023-11-28 18:14:59 +01:00
data : info . clone ( ) ,
} ) ? ,
) ? ;
2023-10-25 22:11:36 +02:00
}
Ok ( info )
}
}
2023-12-04 11:03:43 +01:00
2025-02-04 17:23:06 -05:00
/// Find the Python executable that should be considered the "base" for a virtual environment.
///
/// Assumes that the provided executable is that of a standalone Python interpreter.
///
/// The strategy here mimics that of `getpath.py`: we search up the ancestor path to determine
/// whether a given executable will convert into a valid Python prefix; if not, we resolve the
/// symlink and try again.
///
/// This ensures that:
///
/// 1. We avoid using symlinks to arbitrary locations as the base Python executable. For example,
/// if a user symlinks a Python _executable_ to `/Users/user/foo`, we want to avoid using
/// `/Users/user` as `home`, since it's not a Python installation, and so the relevant libraries
/// and headers won't be found when it's used as the executable directory.
/// See: <https://github.com/python/cpython/blob/a03efb533a58fd13fb0cc7f4a5c02c8406a407bd/Modules/getpath.py#L367-L400>
///
/// 2. We use the "first" resolved symlink that _is_ a valid Python prefix, and thereby preserve
/// symlinks. For example, if a user symlinks a Python _installation_ to `/Users/user/foo`, such
/// that `/Users/user/foo/bin/python` is the resulting executable, we want to use `/Users/user/foo`
/// as `home`, rather than resolving to the symlink target. Concretely, this allows users to
/// symlink patch versions (like `cpython-3.12.6-macos-aarch64-none`) to minor version aliases
/// (like `cpython-3.12-macos-aarch64-none`) and preserve those aliases in the resulting virtual
/// environments.
///
/// See: <https://github.com/python/cpython/blob/a03efb533a58fd13fb0cc7f4a5c02c8406a407bd/Modules/getpath.py#L591-L594>
fn find_base_python (
executable : & Path ,
major : u8 ,
minor : u8 ,
suffix : & str ,
) -> Result < PathBuf , io ::Error > {
/// Returns `true` if `path` is the root directory.
fn is_root ( path : & Path ) -> bool {
let mut components = path . components ( ) ;
components . next ( ) = = Some ( std ::path ::Component ::RootDir ) & & components . next ( ) . is_none ( )
}
/// Determining whether `dir` is a valid Python prefix by searching for a "landmark".
///
/// See: <https://github.com/python/cpython/blob/a03efb533a58fd13fb0cc7f4a5c02c8406a407bd/Modules/getpath.py#L183>
fn is_prefix ( dir : & Path , major : u8 , minor : u8 , suffix : & str ) -> bool {
if cfg! ( windows ) {
dir . join ( " Lib " ) . join ( " os.py " ) . is_file ( )
} else {
dir . join ( " lib " )
. join ( format! ( " python {major} . {minor} {suffix} " ) )
. join ( " os.py " )
. is_file ( )
}
}
let mut executable = Cow ::Borrowed ( executable ) ;
loop {
debug! (
" Assessing Python executable as base candidate: {} " ,
executable . display ( )
) ;
// Determine whether this executable will produce a valid `home` for a virtual environment.
for prefix in executable . ancestors ( ) . take_while ( | path | ! is_root ( path ) ) {
if is_prefix ( prefix , major , minor , suffix ) {
return Ok ( executable . into_owned ( ) ) ;
}
}
// If not, resolve the symlink.
let resolved = fs_err ::read_link ( & executable ) ? ;
// If the symlink is relative, resolve it relative to the executable.
let resolved = if resolved . is_relative ( ) {
if let Some ( parent ) = executable . parent ( ) {
parent . join ( resolved )
} else {
return Err ( io ::Error ::new (
io ::ErrorKind ::Other ,
" Symlink has no parent directory " ,
) ) ;
}
} else {
resolved
} ;
// Normalize the resolved path.
let resolved = uv_fs ::normalize_absolute_path ( & resolved ) ? ;
executable = Cow ::Owned ( resolved ) ;
}
}
2024-01-24 18:27:49 +01:00
#[ cfg(unix) ]
2023-12-04 11:03:43 +01:00
#[ cfg(test) ]
2024-11-18 15:11:46 -05:00
mod tests {
use std ::str ::FromStr ;
use fs_err as fs ;
use indoc ::{ formatdoc , indoc } ;
use tempfile ::tempdir ;
use uv_cache ::Cache ;
use uv_pep440 ::Version ;
use crate ::Interpreter ;
#[ test ]
fn test_cache_invalidation ( ) {
let mock_dir = tempdir ( ) . unwrap ( ) ;
let mocked_interpreter = mock_dir . path ( ) . join ( " python " ) ;
let json = indoc! { r ## "
{
"result": "success",
"platform": {
"os": {
"name": "manylinux",
"major": 2,
"minor": 38
},
"arch": "x86_64"
},
"manylinux_compatible": false,
2024-12-13 14:36:22 -05:00
"standalone": false,
2024-11-18 15:11:46 -05:00
"markers": {
"implementation_name": "cpython",
"implementation_version": "3.12.0",
"os_name": "posix",
"platform_machine": "x86_64",
"platform_python_implementation": "CPython",
"platform_release": "6.5.0-13-generic",
"platform_system": "Linux",
"platform_version": "#13-Ubuntu SMP PREEMPT_DYNAMIC Fri Nov 3 12:16:05 UTC 2023",
"python_full_version": "3.12.0",
"python_version": "3.12",
"sys_platform": "linux"
},
"sys_base_exec_prefix": "/home/ferris/.pyenv/versions/3.12.0",
"sys_base_prefix": "/home/ferris/.pyenv/versions/3.12.0",
"sys_prefix": "/home/ferris/projects/uv/.venv",
"sys_executable": "/home/ferris/projects/uv/.venv/bin/python",
"sys_path": [
"/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/lib/python3.12",
"/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages"
],
"stdlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12",
"scheme": {
"data": "/home/ferris/.pyenv/versions/3.12.0",
"include": "/home/ferris/.pyenv/versions/3.12.0/include",
"platlib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages",
"purelib": "/home/ferris/.pyenv/versions/3.12.0/lib/python3.12/site-packages",
"scripts": "/home/ferris/.pyenv/versions/3.12.0/bin"
},
"virtualenv": {
"data": "",
"include": "include",
"platlib": "lib/python3.12/site-packages",
"purelib": "lib/python3.12/site-packages",
"scripts": "bin"
},
"pointer_size": "64",
"gil_disabled": true
}
"## } ;
let cache = Cache ::temp ( ) . unwrap ( ) . init ( ) . unwrap ( ) ;
fs ::write (
& mocked_interpreter ,
2025-01-11 22:19:33 -05:00
formatdoc! { r "
2025-02-07 10:42:33 -05:00
#!/bin/sh
2024-11-18 15:11:46 -05:00
echo '{json}'
2025-01-11 22:19:33 -05:00
" } ,
2024-11-18 15:11:46 -05:00
)
. unwrap ( ) ;
fs ::set_permissions (
& mocked_interpreter ,
std ::os ::unix ::fs ::PermissionsExt ::from_mode ( 0o770 ) ,
)
. unwrap ( ) ;
let interpreter = Interpreter ::query ( & mocked_interpreter , & cache ) . unwrap ( ) ;
assert_eq! (
interpreter . markers . python_version ( ) . version ,
Version ::from_str ( " 3.12 " ) . unwrap ( )
) ;
fs ::write (
& mocked_interpreter ,
2025-01-11 22:19:33 -05:00
formatdoc! { r "
2025-02-07 10:42:33 -05:00
#!/bin/sh
2024-11-18 15:11:46 -05:00
echo '{}'
2025-01-11 22:19:33 -05:00
" , json . replace ( " 3.12 " , " 3.13 " ) } ,
2024-11-18 15:11:46 -05:00
)
. unwrap ( ) ;
let interpreter = Interpreter ::query ( & mocked_interpreter , & cache ) . unwrap ( ) ;
assert_eq! (
interpreter . markers . python_version ( ) . version ,
Version ::from_str ( " 3.13 " ) . unwrap ( )
) ;
}
}