2024-09-08 18:33:34 -04:00
use owo_colors ::OwoColorize ;
2024-06-06 16:15:28 -04:00
use std ::borrow ::Cow ;
2023-10-05 15:09:22 -04:00
use std ::env ;
2024-06-28 11:16:59 -04:00
use std ::fmt ;
2023-10-25 22:11:36 +02:00
use std ::path ::{ Path , PathBuf } ;
2024-05-17 11:47:30 -04:00
use std ::sync ::Arc ;
2024-02-15 11:19:46 -06:00
use uv_cache ::Cache ;
2024-10-01 20:15:32 -04:00
use uv_cache_key ::cache_digest ;
2024-02-28 20:44:50 -05:00
use uv_fs ::{ LockedFile , Simplified } ;
2023-11-23 09:57:33 +01:00
2024-07-03 08:44:29 -04:00
use crate ::discovery ::find_python_installation ;
use crate ::installation ::PythonInstallation ;
2024-05-21 15:37:23 -04:00
use crate ::virtualenv ::{ virtualenv_python_executable , PyVenvConfiguration } ;
2024-06-20 13:54:17 -04:00
use crate ::{
2024-07-03 08:44:29 -04:00
EnvironmentPreference , Error , Interpreter , Prefix , PythonNotFound , PythonPreference ,
PythonRequest , Target ,
2024-06-20 13:54:17 -04:00
} ;
2023-10-05 15:09:22 -04:00
2024-03-10 14:44:50 +01:00
/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
2023-10-25 22:11:36 +02:00
#[ derive(Debug, Clone) ]
2024-05-17 11:47:30 -04:00
pub struct PythonEnvironment ( Arc < PythonEnvironmentShared > ) ;
#[ derive(Debug, Clone) ]
struct PythonEnvironmentShared {
2023-10-25 22:11:36 +02:00
root : PathBuf ,
2023-11-23 09:57:33 +01:00
interpreter : Interpreter ,
2023-10-25 22:11:36 +02:00
}
2024-06-28 11:16:59 -04:00
/// The result of failed environment discovery.
///
2024-07-03 08:44:29 -04:00
/// Generally this is cast from [`PythonNotFound`] by [`PythonEnvironment::find`].
2024-06-28 11:16:59 -04:00
#[ derive(Clone, Debug, Error) ]
pub struct EnvironmentNotFound {
2024-07-03 08:44:29 -04:00
request : PythonRequest ,
2024-06-28 11:16:59 -04:00
preference : EnvironmentPreference ,
}
2024-09-19 06:49:35 -05:00
#[ derive(Clone, Debug, Error) ]
pub struct InvalidEnvironment {
path : PathBuf ,
2024-09-20 12:28:17 -05:00
pub kind : InvalidEnvironmentKind ,
}
#[ derive(Debug, Clone) ]
pub enum InvalidEnvironmentKind {
NotDirectory ,
2024-11-25 16:23:49 -06:00
Empty ,
2024-09-20 12:28:17 -05:00
MissingExecutable ( PathBuf ) ,
2024-09-19 06:49:35 -05:00
}
2024-07-03 08:44:29 -04:00
impl From < PythonNotFound > for EnvironmentNotFound {
fn from ( value : PythonNotFound ) -> Self {
2024-06-28 11:16:59 -04:00
Self {
request : value . request ,
preference : value . environment_preference ,
}
}
}
impl fmt ::Display for EnvironmentNotFound {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2024-09-08 18:33:34 -04:00
#[ derive(Debug, Copy, Clone) ]
enum SearchType {
/// Only virtual environments were searched.
Virtual ,
/// Only system installations were searched.
System ,
/// Both virtual and system installations were searched.
VirtualOrSystem ,
}
impl fmt ::Display for SearchType {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
match self {
Self ::Virtual = > write! ( f , " virtual environment " ) ,
Self ::System = > write! ( f , " system Python installation " ) ,
Self ::VirtualOrSystem = > {
write! ( f , " virtual environment or system Python installation " )
}
}
}
}
let search_type = match self . preference {
EnvironmentPreference ::Any = > SearchType ::VirtualOrSystem ,
2024-06-28 11:16:59 -04:00
EnvironmentPreference ::ExplicitSystem = > {
if self . request . is_explicit_system ( ) {
2024-09-08 18:33:34 -04:00
SearchType ::VirtualOrSystem
2024-06-28 11:16:59 -04:00
} else {
2024-09-08 18:33:34 -04:00
SearchType ::Virtual
2024-06-28 11:16:59 -04:00
}
}
2024-09-08 18:33:34 -04:00
EnvironmentPreference ::OnlySystem = > SearchType ::System ,
EnvironmentPreference ::OnlyVirtual = > SearchType ::Virtual ,
2024-06-28 11:16:59 -04:00
} ;
2024-09-08 18:33:34 -04:00
2024-09-19 06:19:13 -05:00
if matches! ( self . request , PythonRequest ::Default | PythonRequest ::Any ) {
2024-09-08 18:33:34 -04:00
write! ( f , " No {search_type} found " ) ? ;
} else {
write! ( f , " No {search_type} found for {} " , self . request ) ? ;
2024-06-28 11:16:59 -04:00
}
2024-09-08 18:33:34 -04:00
match search_type {
// This error message assumes that the relevant API accepts the `--system` flag. This
// is true of the callsites today, since the project APIs never surface this error.
SearchType ::Virtual = > write! ( f , " ; run `{}` to create an environment, or pass `{}` to install into a non-virtual environment " , " uv venv " . green ( ) , " --system " . green ( ) ) ? ,
SearchType ::VirtualOrSystem = > write! ( f , " ; run `{}` to create an environment " , " uv venv " . green ( ) ) ? ,
SearchType ::System = > { }
}
Ok ( ( ) )
2024-06-28 11:16:59 -04:00
}
}
2024-12-25 14:18:01 -05:00
impl fmt ::Display for InvalidEnvironment {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2024-09-19 06:49:35 -05:00
write! (
f ,
" Invalid environment at `{}`: {} " ,
self . path . user_display ( ) ,
2024-09-20 12:28:17 -05:00
self . kind
2024-09-19 06:49:35 -05:00
)
}
}
2024-12-25 14:18:01 -05:00
impl fmt ::Display for InvalidEnvironmentKind {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> fmt ::Result {
2024-09-20 12:28:17 -05:00
match self {
Self ::NotDirectory = > write! ( f , " expected directory but found a file " ) ,
Self ::MissingExecutable ( path ) = > {
write! ( f , " missing Python executable at `{}` " , path . user_display ( ) )
}
2024-11-25 16:23:49 -06:00
Self ::Empty = > write! ( f , " directory is empty " ) ,
2024-09-20 12:28:17 -05:00
}
}
}
2024-02-28 10:04:55 -05:00
impl PythonEnvironment {
2024-06-20 13:54:17 -04:00
/// Find a [`PythonEnvironment`] matching the given request and preference.
///
2024-07-03 08:44:29 -04:00
/// If looking for a Python interpreter to create a new environment, use [`PythonInstallation::find`]
2024-06-20 13:54:17 -04:00
/// instead.
pub fn find (
2024-07-03 08:44:29 -04:00
request : & PythonRequest ,
2024-06-20 13:54:17 -04:00
preference : EnvironmentPreference ,
cache : & Cache ,
) -> Result < Self , Error > {
2024-07-03 08:44:29 -04:00
let installation = match find_python_installation (
2024-06-20 13:54:17 -04:00
request ,
preference ,
2024-07-03 08:44:29 -04:00
// Ignore managed installations when looking for environments
PythonPreference ::OnlySystem ,
2024-06-20 13:54:17 -04:00
cache ,
2024-06-28 11:16:59 -04:00
) ? {
2024-07-03 08:44:29 -04:00
Ok ( installation ) = > installation ,
2024-06-28 11:16:59 -04:00
Err ( err ) = > return Err ( EnvironmentNotFound ::from ( err ) . into ( ) ) ,
} ;
2024-07-03 08:44:29 -04:00
Ok ( Self ::from_installation ( installation ) )
2024-06-20 13:54:17 -04:00
}
2024-05-10 10:10:13 -04:00
/// Create a [`PythonEnvironment`] from the virtual environment at the given root.
2024-09-20 12:28:17 -05:00
///
/// N.B. This function also works for system Python environments and users depend on this.
2024-06-07 15:20:28 -04:00
pub fn from_root ( root : impl AsRef < Path > , cache : & Cache ) -> Result < Self , Error > {
let venv = match fs_err ::canonicalize ( root . as_ref ( ) ) {
2024-05-10 10:10:13 -04:00
Ok ( venv ) = > venv ,
Err ( err ) if err . kind ( ) = = std ::io ::ErrorKind ::NotFound = > {
2024-06-28 11:16:59 -04:00
return Err ( Error ::MissingEnvironment ( EnvironmentNotFound {
preference : EnvironmentPreference ::Any ,
2024-07-03 08:44:29 -04:00
request : PythonRequest ::Directory ( root . as_ref ( ) . to_owned ( ) ) ,
2024-06-28 11:16:59 -04:00
} ) ) ;
2024-05-10 10:10:13 -04:00
}
2024-05-21 15:37:23 -04:00
Err ( err ) = > return Err ( Error ::Discovery ( err . into ( ) ) ) ,
2024-05-10 10:10:13 -04:00
} ;
2024-09-19 06:49:35 -05:00
if venv . is_file ( ) {
return Err ( InvalidEnvironment {
path : venv ,
2024-09-20 12:28:17 -05:00
kind : InvalidEnvironmentKind ::NotDirectory ,
2024-09-19 06:49:35 -05:00
}
. into ( ) ) ;
}
2024-11-25 16:23:49 -06:00
if venv . read_dir ( ) . is_ok_and ( | mut dir | dir . next ( ) . is_none ( ) ) {
return Err ( InvalidEnvironment {
path : venv ,
kind : InvalidEnvironmentKind ::Empty ,
}
. into ( ) ) ;
}
2024-09-20 12:28:17 -05:00
let executable = virtualenv_python_executable ( & venv ) ;
// Check if the executable exists before querying so we can provide a more specific error
// Note we intentionally don't require a resolved link to exist here, we're just trying to
// tell if this _looks_ like a Python environment.
if ! ( executable . is_symlink ( ) | | executable . is_file ( ) ) {
2024-09-19 06:49:35 -05:00
return Err ( InvalidEnvironment {
path : venv ,
2024-09-20 12:28:17 -05:00
kind : InvalidEnvironmentKind ::MissingExecutable ( executable . clone ( ) ) ,
2024-09-19 06:49:35 -05:00
}
. into ( ) ) ;
2024-09-20 12:28:17 -05:00
} ;
2024-09-19 06:49:35 -05:00
2024-05-21 15:37:23 -04:00
let interpreter = Interpreter ::query ( executable , cache ) ? ;
2024-02-22 08:47:33 +01:00
2024-05-17 11:47:30 -04:00
Ok ( Self ( Arc ::new ( PythonEnvironmentShared {
2024-06-05 22:11:21 -04:00
root : interpreter . sys_prefix ( ) . to_path_buf ( ) ,
2023-11-23 09:57:33 +01:00
interpreter ,
2024-05-17 11:47:30 -04:00
} ) ) )
2023-10-25 22:11:36 +02:00
}
2024-07-03 08:44:29 -04:00
/// Create a [`PythonEnvironment`] from an existing [`PythonInstallation`].
pub fn from_installation ( installation : PythonInstallation ) -> Self {
Self ::from_interpreter ( installation . into_interpreter ( ) )
2024-02-28 09:48:49 -05:00
}
2024-05-21 15:37:23 -04:00
/// Create a [`PythonEnvironment`] from an existing [`Interpreter`].
2024-03-07 06:04:02 -08:00
pub fn from_interpreter ( interpreter : Interpreter ) -> Self {
2024-05-17 11:47:30 -04:00
Self ( Arc ::new ( PythonEnvironmentShared {
2024-06-05 22:11:21 -04:00
root : interpreter . sys_prefix ( ) . to_path_buf ( ) ,
2024-03-07 06:04:02 -08:00
interpreter ,
2024-05-17 11:47:30 -04:00
} ) )
2024-03-01 10:52:48 -05:00
}
2024-04-25 19:15:39 -04:00
/// Create a [`PythonEnvironment`] from an existing [`Interpreter`] and `--target` directory.
2024-07-08 09:15:25 -05:00
pub fn with_target ( self , target : Target ) -> std ::io ::Result < Self > {
2024-05-17 11:47:30 -04:00
let inner = Arc ::unwrap_or_clone ( self . 0 ) ;
2024-07-08 09:15:25 -05:00
Ok ( Self ( Arc ::new ( PythonEnvironmentShared {
interpreter : inner . interpreter . with_target ( target ) ? ,
2024-05-17 11:47:30 -04:00
.. inner
2024-07-08 09:15:25 -05:00
} ) ) )
2024-04-25 19:15:39 -04:00
}
2024-06-06 16:15:28 -04:00
/// Create a [`PythonEnvironment`] from an existing [`Interpreter`] and `--prefix` directory.
2024-07-08 09:15:25 -05:00
pub fn with_prefix ( self , prefix : Prefix ) -> std ::io ::Result < Self > {
2024-06-06 16:15:28 -04:00
let inner = Arc ::unwrap_or_clone ( self . 0 ) ;
2024-07-08 09:15:25 -05:00
Ok ( Self ( Arc ::new ( PythonEnvironmentShared {
interpreter : inner . interpreter . with_prefix ( prefix ) ? ,
2024-06-06 16:15:28 -04:00
.. inner
2024-07-08 09:15:25 -05:00
} ) ) )
2024-06-06 16:15:28 -04:00
}
2024-04-25 19:15:39 -04:00
/// Returns the root (i.e., `prefix`) of the Python interpreter.
2023-10-25 22:11:36 +02:00
pub fn root ( & self ) -> & Path {
2024-05-17 11:47:30 -04:00
& self . 0. root
2023-10-25 22:11:36 +02:00
}
2023-12-04 09:31:00 -05:00
/// Return the [`Interpreter`] for this virtual environment.
2024-05-21 15:37:23 -04:00
///
/// See also [`PythonEnvironment::into_interpreter`].
2023-11-23 09:57:33 +01:00
pub fn interpreter ( & self ) -> & Interpreter {
2024-05-17 11:47:30 -04:00
& self . 0. interpreter
2023-10-25 22:11:36 +02:00
}
2024-05-21 15:37:23 -04:00
/// Return the [`PyVenvConfiguration`] for this environment, as extracted from the
2023-12-04 09:31:00 -05:00
/// `pyvenv.cfg` file.
2024-02-23 18:11:22 +01:00
pub fn cfg ( & self ) -> Result < PyVenvConfiguration , Error > {
2024-05-17 11:47:30 -04:00
Ok ( PyVenvConfiguration ::parse ( self . 0. root . join ( " pyvenv.cfg " ) ) ? )
2023-12-04 09:31:00 -05:00
}
2024-07-29 01:10:11 +01:00
/// Returns `true` if the environment is "relocatable".
pub fn relocatable ( & self ) -> bool {
self . cfg ( ) . is_ok_and ( | cfg | cfg . is_relocatable ( ) )
}
2024-02-27 21:10:29 -05:00
/// Returns the location of the Python executable.
pub fn python_executable ( & self ) -> & Path {
2024-05-17 11:47:30 -04:00
self . 0. interpreter . sys_executable ( )
2024-02-27 21:10:29 -05:00
}
2024-05-21 15:37:23 -04:00
/// Returns an iterator over the `site-packages` directories inside the environment.
2024-03-18 20:06:16 -07:00
///
/// 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.
2024-04-12 17:08:56 -04:00
///
/// Some distributions also create symbolic links from `purelib` to `platlib`; in such cases, we
/// still deduplicate the entries, returning a single path.
2024-06-06 16:15:28 -04:00
pub fn site_packages ( & self ) -> impl Iterator < Item = Cow < Path > > {
2024-07-07 20:23:59 -05:00
self . 0. interpreter . site_packages ( )
2023-10-25 22:11:36 +02:00
}
2023-12-12 15:46:37 +01:00
2024-05-21 15:37:23 -04:00
/// Returns the path to the `bin` directory inside this environment.
2024-02-27 21:10:29 -05:00
pub fn scripts ( & self ) -> & Path {
2024-05-17 11:47:30 -04:00
self . 0. interpreter . scripts ( )
2023-12-12 15:46:37 +01:00
}
2023-12-18 16:44:45 -05:00
2024-05-21 15:37:23 -04:00
/// Grab a file lock for the environment to prevent concurrent writes across processes.
2024-08-29 11:16:14 -05:00
pub async fn lock ( & self ) -> Result < LockedFile , std ::io ::Error > {
2024-05-17 11:47:30 -04:00
if let Some ( target ) = self . 0. interpreter . target ( ) {
2024-07-25 10:22:36 -04:00
// If we're installing into a `--target`, use a target-specific lockfile.
2024-08-29 11:16:14 -05:00
LockedFile ::acquire ( target . root ( ) . join ( " .lock " ) , target . root ( ) . user_display ( ) ) . await
2024-06-25 06:47:52 -04:00
} else if let Some ( prefix ) = self . 0. interpreter . prefix ( ) {
2024-07-25 10:22:36 -04:00
// Likewise, if we're installing into a `--prefix`, use a prefix-specific lockfile.
2024-08-29 11:16:14 -05:00
LockedFile ::acquire ( prefix . root ( ) . join ( " .lock " ) , prefix . root ( ) . user_display ( ) ) . await
2024-05-17 11:47:30 -04:00
} else if self . 0. interpreter . is_virtualenv ( ) {
2024-07-25 10:22:36 -04:00
// If the environment a virtualenv, use a virtualenv-specific lockfile.
2024-08-29 11:16:14 -05:00
LockedFile ::acquire ( self . 0. root . join ( " .lock " ) , self . 0. root . user_display ( ) ) . await
2024-02-28 11:03:02 -05:00
} else {
2024-07-25 10:22:36 -04:00
// Otherwise, use a global lockfile.
2024-02-28 11:03:02 -05:00
LockedFile ::acquire (
2024-10-01 20:15:32 -04:00
env ::temp_dir ( ) . join ( format! ( " uv- {} .lock " , cache_digest ( & self . 0. root ) ) ) ,
2024-05-23 10:27:07 -04:00
self . 0. root . user_display ( ) ,
2024-02-28 11:03:02 -05:00
)
2024-08-29 11:16:14 -05:00
. await
2024-02-28 11:03:02 -05:00
}
2023-12-18 16:44:45 -05:00
}
2024-04-17 11:32:04 -05:00
2024-05-21 15:37:23 -04:00
/// Return the [`Interpreter`] for this environment.
///
/// See also [`PythonEnvironment::interpreter`].
2024-04-17 11:32:04 -05:00
pub fn into_interpreter ( self ) -> Interpreter {
2024-05-17 11:47:30 -04:00
Arc ::unwrap_or_clone ( self . 0 ) . interpreter
2024-04-17 11:32:04 -05:00
}
2024-09-25 10:40:28 -07:00
/// Returns `true` if the [`PythonEnvironment`] uses the same underlying [`Interpreter`].
pub fn uses ( & self , interpreter : & Interpreter ) -> bool {
// TODO(zanieb): Consider using `sysconfig.get_path("stdlib")` instead, which
// should be generally robust.
if cfg! ( windows ) {
// On Windows, we can't canonicalize an interpreter based on its executable path
// because the executables are separate shim files (not links). Instead, we
// compare the `sys.base_prefix`.
let old_base_prefix = self . interpreter ( ) . sys_base_prefix ( ) ;
let selected_base_prefix = interpreter . sys_base_prefix ( ) ;
old_base_prefix = = selected_base_prefix
} else {
// On Unix, we can see if the canonicalized executable is the same file.
self . interpreter ( ) . sys_executable ( ) = = interpreter . sys_executable ( )
| | same_file ::is_same_file (
self . interpreter ( ) . sys_executable ( ) ,
interpreter . sys_executable ( ) ,
)
. unwrap_or ( false )
}
}
2023-10-25 22:11:36 +02:00
}