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 ;
2024-05-21 15:37:23 -04: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 ;
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 ;
use tracing ::{ trace , warn } ;
2023-10-25 22:11:36 +02:00
2024-09-09 16:19:15 -04:00
use uv_cache ::{ Cache , CacheBucket , CachedByTimestamp , Freshness } ;
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 ,
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
}
2024-07-04 13:38:53 -04:00
/// Return the [`Interpreter`] for the base executable, if it's available.
///
/// If no such base executable is available, or if the base executable is the same as the
/// current executable, this method returns `None`.
pub fn to_base_interpreter ( & self , cache : & Cache ) -> Result < Option < Self > , Error > {
if let Some ( base_executable ) = self
. sys_base_executable ( )
. filter ( | base_executable | * base_executable ! = self . sys_executable ( ) )
{
match Self ::query ( base_executable , cache ) {
Ok ( base_interpreter ) = > Ok ( Some ( base_interpreter ) ) ,
Err ( Error ::NotFound ( _ ) ) = > Ok ( None ) ,
Err ( err ) = > Err ( err ) ,
}
} else {
Ok ( None )
}
}
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
}
}
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 ,
} ,
#[ error( " Querying Python at `{}` did not return the expected data \n {err} \n --- stdout: \n {stdout} \n --- stderr: \n {stderr} \n --- " , path.display()) ]
UnexpectedResponse {
err : serde_json ::Error ,
stdout : String ,
stderr : String ,
path : PathBuf ,
} ,
#[ error( " Querying Python at `{}` failed with exit status {code} \n --- stdout: \n {stdout} \n --- stderr: \n {stderr} \n --- " , path.display()) ]
StatusCode {
code : ExitStatus ,
stdout : String ,
stderr : String ,
path : PathBuf ,
} ,
#[ 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 ,
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 ( ) ,
} ) ;
}
return Err ( Error ::StatusCode {
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 ( ) ,
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 {
Error ::UnexpectedResponse {
err ,
stdout : String ::from_utf8_lossy ( & output . stdout ) . trim ( ) . to_string ( ) ,
stderr ,
path : interpreter . to_path_buf ( ) ,
}
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 ,
2024-12-12 13:21:35 -05:00
// Shard interpreter metadata by host architecture, to avoid cache collisions when
// running universal binaries under Rosetta.
ARCH ,
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.
2024-01-23 18:30:26 -05:00
if cache
. freshness ( & cache_entry , None )
. is_ok_and ( Freshness ::is_fresh )
{
if let Ok ( data ) = fs ::read ( cache_entry . path ( ) ) {
2024-01-24 11:21:31 -08:00
match rmp_serde ::from_slice ::< CachedByTimestamp < Self > > ( & data ) {
2024-01-23 18:30:26 -05:00
Ok ( cached ) = > {
if cached . timestamp = = modified {
2024-05-21 15:37:23 -04:00
trace! (
2024-02-23 19:43:46 +01:00
" Cached interpreter info for Python {}, skipping probing: {} " ,
2024-05-08 19:01:44 -04:00
cached . data . markers . python_full_version ( ) ,
2024-03-20 09:52:50 -04:00
executable . user_display ( )
2024-02-23 19:43:46 +01:00
) ;
2024-01-23 18:30:26 -05:00
return Ok ( cached . data ) ;
}
2024-05-21 15:37:23 -04:00
trace! (
" Ignoring stale interpreter markers for: {} " ,
2024-03-20 09:52:50 -04:00
executable . user_display ( )
2024-01-23 18:30:26 -05:00
) ;
}
Err ( err ) = > {
warn! (
2024-05-21 15:37:23 -04:00
" Broken interpreter cache entry at {}, removing: {err} " ,
2024-03-20 09:52:50 -04:00
cache_entry . path ( ) . user_display ( )
2024-01-23 18:30:26 -05:00
) ;
let _ = fs_err ::remove_file ( cache_entry . path ( ) ) ;
2023-12-16 22:01:35 +01:00
}
2023-12-03 17:44:43 -05:00
}
2023-10-25 22:11:36 +02:00
}
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 ( ) ,
2023-12-16 22:01:35 +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
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 "
2024-11-18 15:11:46 -05:00
#!/bin/bash
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 "
2024-11-18 15:11:46 -05:00
#!/bin/bash
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 ( )
) ;
}
}