-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
Copy pathpython_environment.rs
189 lines (166 loc) · 6.46 KB
/
python_environment.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
use std::env;
use std::path::{Path, PathBuf};
use tracing::debug;
use platform_host::Platform;
use uv_cache::Cache;
use uv_fs::{LockedFile, Simplified};
use crate::cfg::PyVenvConfiguration;
use crate::{find_default_python, find_requested_python, Error, Interpreter};
/// A Python environment, consisting of a Python [`Interpreter`] and its associated paths.
#[derive(Debug, Clone)]
pub struct PythonEnvironment {
root: PathBuf,
interpreter: Interpreter,
}
impl PythonEnvironment {
/// Create a [`PythonEnvironment`] for an existing virtual environment.
pub fn from_virtualenv(platform: Platform, cache: &Cache) -> Result<Self, Error> {
let Some(venv) = detect_virtual_env()? else {
return Err(Error::VenvNotFound);
};
let venv = fs_err::canonicalize(venv)?;
let executable = detect_python_executable(&venv);
let interpreter = Interpreter::query(&executable, platform, cache)?;
debug_assert!(
interpreter.base_prefix() == interpreter.base_exec_prefix(),
"Not a virtualenv (Python: {}, prefix: {})",
executable.display(),
interpreter.base_prefix().display()
);
Ok(Self {
root: venv,
interpreter,
})
}
/// Create a [`PythonEnvironment`] for a Python interpreter specifier (e.g., a path or a binary name).
pub fn from_requested_python(
python: &str,
platform: &Platform,
cache: &Cache,
) -> Result<Self, Error> {
let Some(interpreter) = find_requested_python(python, platform, cache)? else {
return Err(Error::RequestedPythonNotFound(python.to_string()));
};
Ok(Self {
root: interpreter.prefix().to_path_buf(),
interpreter,
})
}
/// Create a [`PythonEnvironment`] for the default Python interpreter.
pub fn from_default_python(platform: &Platform, cache: &Cache) -> Result<Self, Error> {
let interpreter = find_default_python(platform, cache)?;
Ok(Self {
root: interpreter.prefix().to_path_buf(),
interpreter,
})
}
/// Create a [`PythonEnvironment`] from an existing [`Interpreter`] and root directory.
pub fn from_interpreter(interpreter: Interpreter) -> Self {
Self {
root: interpreter.prefix().to_path_buf(),
interpreter,
}
}
/// Returns the location of the Python interpreter.
pub fn root(&self) -> &Path {
&self.root
}
/// Return the [`Interpreter`] for this virtual environment.
pub fn interpreter(&self) -> &Interpreter {
&self.interpreter
}
/// Return the [`PyVenvConfiguration`] for this virtual environment, as extracted from the
/// `pyvenv.cfg` file.
pub fn cfg(&self) -> Result<PyVenvConfiguration, Error> {
Ok(PyVenvConfiguration::parse(self.root.join("pyvenv.cfg"))?)
}
/// Returns the location of the Python executable.
pub fn python_executable(&self) -> &Path {
self.interpreter.sys_executable()
}
/// Returns the path to the `site-packages` directory inside a virtual environment.
pub fn site_packages(&self) -> &Path {
self.interpreter.purelib()
}
/// Returns the path to the `bin` directory inside a virtual environment.
pub fn scripts(&self) -> &Path {
self.interpreter.scripts()
}
/// Grab a file lock for the virtual environment to prevent concurrent writes across processes.
pub fn lock(&self) -> Result<LockedFile, std::io::Error> {
if self.interpreter.is_virtualenv() {
// If the environment a virtualenv, use a virtualenv-specific lock file.
LockedFile::acquire(self.root.join(".lock"), self.root.simplified_display())
} else {
// Otherwise, use a global lock file.
LockedFile::acquire(
env::temp_dir().join(format!("uv-{}.lock", cache_key::digest(&self.root))),
self.root.simplified_display(),
)
}
}
}
/// Locate the current virtual environment.
pub(crate) fn detect_virtual_env() -> Result<Option<PathBuf>, Error> {
match (
env::var_os("VIRTUAL_ENV").filter(|value| !value.is_empty()),
env::var_os("CONDA_PREFIX").filter(|value| !value.is_empty()),
) {
(Some(dir), None) => {
debug!(
"Found a virtualenv through VIRTUAL_ENV at: {}",
Path::new(&dir).display()
);
return Ok(Some(PathBuf::from(dir)));
}
(None, Some(dir)) => {
debug!(
"Found a virtualenv through CONDA_PREFIX at: {}",
Path::new(&dir).display()
);
return Ok(Some(PathBuf::from(dir)));
}
(Some(venv), Some(conda)) if venv == conda => return Ok(Some(PathBuf::from(venv))),
(Some(_), Some(_)) => {
return Err(Error::Conflict);
}
(None, None) => {
// No environment variables set. Try to find a virtualenv in the current directory.
}
};
// Search for a `.venv` directory in the current or any parent directory.
let current_dir = env::current_dir().expect("Failed to detect current directory");
for dir in current_dir.ancestors() {
let dot_venv = dir.join(".venv");
if dot_venv.is_dir() {
if !dot_venv.join("pyvenv.cfg").is_file() {
return Err(Error::MissingPyVenvCfg(dot_venv));
}
debug!("Found a virtualenv named .venv at: {}", dot_venv.display());
return Ok(Some(dot_venv));
}
}
Ok(None)
}
/// Returns the path to the `python` executable inside a virtual environment.
pub(crate) fn detect_python_executable(venv: impl AsRef<Path>) -> PathBuf {
let venv = venv.as_ref();
if cfg!(windows) {
// Search for `python.exe` in the `Scripts` directory.
let executable = venv.join("Scripts").join("python.exe");
if executable.exists() {
return executable;
}
// Apparently, Python installed via msys2 on Windows _might_ produce a POSIX-like layout.
// See: https://github.com/PyO3/maturin/issues/1108
let executable = venv.join("bin").join("python.exe");
if executable.exists() {
return executable;
}
// Fallback for Conda environments.
venv.join("python.exe")
} else {
// Search for `python` in the `bin` directory.
venv.join("bin").join("python")
}
}