Skip to content

Commit

Permalink
Remove nesting
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Apr 22, 2024
1 parent 6368cc6 commit ba69272
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 150 deletions.
315 changes: 166 additions & 149 deletions crates/uv-virtualenv/src/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,156 +171,23 @@ pub fn create_bare_venv(
}

// No symlinking on Windows, at least not on a regular non-dev non-admin Windows install.
#[cfg(windows)]
{
// https://github.com/python/cpython/blob/d457345bbc6414db0443819290b04a9a4333313d/Lib/venv/__init__.py#L261-L267
// https://github.com/pypa/virtualenv/blob/d9fdf48d69f0d0ca56140cf0381edbb5d6fe09f5/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py#L78-L83
// There's two kinds of applications on windows: Those that allocate a console (python.exe)
// and those that don't because they use window(s) (pythonw.exe).

// First priority: the `python.exe` and `pythonw.exe` shims.
for python_exe in ["python.exe", "pythonw.exe"] {
let shim = interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(python_exe);
match fs_err::copy(shim, scripts.join(python_exe)) {
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// Second priority: the `venvlauncher.exe` and `venvwlauncher.exe` shims.
// These are equivalent to the `python.exe` and `pythonw.exe` shims, which were
// renamed in Python 3.13.
let launcher = match python_exe {
"python.exe" => "venvlauncher.exe",
"pythonw.exe" => "venvwlauncher.exe",
_ => unreachable!(),
};
let shim = interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(launcher);

match fs_err::copy(shim, scripts.join(python_exe)) {
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// Third priority: on Conda at least, we can look for the launcher shim next to
// the Python executable itself.
let shim = base_python.with_file_name(launcher);
match fs_err::copy(shim, scripts.join(python_exe)) {
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
// Fourth priority: if the launcher shim doesn't exist, assume this is
// an embedded Python. Copy the Python executable itself, along with
// the DLLs, `.pyd` files, and `.zip` files in the same directory.
match fs_err::copy(
base_python.with_file_name(python_exe),
scripts.join(python_exe),
) {
Ok(_) => {}
Err(err) if err.kind() == io::ErrorKind::NotFound => {
return Err(Error::IO(io::Error::new(
io::ErrorKind::NotFound,
format!(
"Could not find a suitable Python executable for the virtual environment (tried: {}, {}, {}, {})",
interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(python_exe)
.user_display(),
interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(launcher)
.user_display(),
base_python.with_file_name(launcher).user_display(),
base_python
.with_file_name(python_exe)
.user_display(),
)
)));
}
Err(err) => {
return Err(err.into());
}
}

// Copy `.dll` and `.pyd` files from the top-level, and from the
// `DLLs` subdirectory (if it exists).
for directory in [
python_home,
interpreter.base_prefix().join("DLLs").as_path(),
] {
let entries = match fs_err::read_dir(directory) {
Ok(read_dir) => read_dir,
Err(err) if err.kind() == io::ErrorKind::NotFound => {
continue;
}
Err(err) => {
return Err(err.into());
}
};
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|ext| {
ext.eq_ignore_ascii_case("dll")
|| ext.eq_ignore_ascii_case("pyd")
}) {
if let Some(file_name) = path.file_name() {
fs_err::copy(&path, scripts.join(file_name))?;
}
}
}
}

// Copy `.zip` files from the top-level.
let entries = match fs_err::read_dir(python_home) {
Ok(read_dir) => read_dir,
Err(err) if err.kind() == io::ErrorKind::NotFound => {
continue;
}
Err(err) => {
return Err(err.into());
}
};

for entry in entries {
let entry = entry?;
let path = entry.path();
if path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
if let Some(file_name) = path.file_name() {
fs_err::copy(&path, scripts.join(file_name))?;
}
}
}
}
Err(err) => {
return Err(err.into());
}
}
}
Err(err) => {
return Err(err.into());
}
}
}
Err(err) => {
return Err(err.into());
}
}
}
if cfg!(windows) {
copy_launcher_windows(
WindowsExecutable::Python,
interpreter,
&base_python,
&scripts,
python_home,
)?;
copy_launcher_windows(
WindowsExecutable::Pythonw,
interpreter,
&base_python,
&scripts,
python_home,
)?;
}

#[cfg(not(any(unix, windows)))]
{
compile_error!("Only Windows and Unix are supported")
Expand Down Expand Up @@ -422,3 +289,153 @@ pub fn create_bare_venv(
executable,
})
}

#[derive(Debug, Copy, Clone)]
enum WindowsExecutable {
/// The `python.exe` executable (or `venvlauncher.exe` launcher shim).
Python,
/// The `pythonw.exe` executable (or `venvwlauncher.exe` launcher shim).
Pythonw,
}

impl WindowsExecutable {
/// The name of the Python executable.
fn exe(self) -> &'static str {
match self {
WindowsExecutable::Python => "python.exe",
WindowsExecutable::Pythonw => "pythonw.exe",
}
}

/// The name of the launcher shim.
fn launcher(self) -> &'static str {
match self {
WindowsExecutable::Python => "venvlauncher.exe",
WindowsExecutable::Pythonw => "venvwlauncher.exe",
}
}
}

/// <https://github.com/python/cpython/blob/d457345bbc6414db0443819290b04a9a4333313d/Lib/venv/__init__.py#L261-L267>
/// <https://github.com/pypa/virtualenv/blob/d9fdf48d69f0d0ca56140cf0381edbb5d6fe09f5/src/virtualenv/create/via_global_ref/builtin/cpython/cpython3.py#L78-L83>
///
/// There's two kinds of applications on windows: Those that allocate a console (python.exe)
/// and those that don't because they use window(s) (pythonw.exe).
fn copy_launcher_windows(
executable: WindowsExecutable,
interpreter: &Interpreter,
base_python: &Path,
scripts: &Path,
python_home: &Path,
) -> Result<(), Error> {
// First priority: the `python.exe` and `pythonw.exe` shims.
let shim = interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(executable.exe());
match fs_err::copy(shim, scripts.join(executable.exe())) {
Ok(_) => return Ok(()),
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err.into());
}
}

// Second priority: the `venvlauncher.exe` and `venvwlauncher.exe` shims.
// These are equivalent to the `python.exe` and `pythonw.exe` shims, which were
// renamed in Python 3.13.
let shim = interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(executable.launcher());
match fs_err::copy(shim, scripts.join(executable.exe())) {
Ok(_) => return Ok(()),
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err.into());
}
}

// Third priority: on Conda at least, we can look for the launcher shim next to
// the Python executable itself.
let shim = base_python.with_file_name(executable.launcher());
match fs_err::copy(shim, scripts.join(executable.exe())) {
Ok(_) => return Ok(()),
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err.into());
}
}

// Fourth priority: if the launcher shim doesn't exist, assume this is
// an embedded Python. Copy the Python executable itself, along with
// the DLLs, `.pyd` files, and `.zip` files in the same directory.
match fs_err::copy(
base_python.with_file_name(executable.exe()),
scripts.join(executable.exe()),
) {
Ok(_) => {
// Copy `.dll` and `.pyd` files from the top-level, and from the
// `DLLs` subdirectory (if it exists).
for directory in [
python_home,
interpreter.base_prefix().join("DLLs").as_path(),
] {
let entries = match fs_err::read_dir(directory) {
Ok(read_dir) => read_dir,
Err(err) if err.kind() == io::ErrorKind::NotFound => {
continue;
}
Err(err) => {
return Err(err.into());
}
};
for entry in entries {
let entry = entry?;
let path = entry.path();
if path.extension().is_some_and(|ext| {
ext.eq_ignore_ascii_case("dll") || ext.eq_ignore_ascii_case("pyd")
}) {
if let Some(file_name) = path.file_name() {
fs_err::copy(&path, scripts.join(file_name))?;
}
}
}
}

// Copy `.zip` files from the top-level.
match fs_err::read_dir(python_home) {
Ok(entries) => {
for entry in entries {
let entry = entry?;
let path = entry.path();
if path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
{
if let Some(file_name) = path.file_name() {
fs_err::copy(&path, scripts.join(file_name))?;
}
}
}
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err.into());
}
};

return Ok(());
}
Err(err) if err.kind() == io::ErrorKind::NotFound => {}
Err(err) => {
return Err(err.into());
}
}

Err(Error::NotFound(base_python.user_display().to_string()))
}
4 changes: 3 additions & 1 deletion crates/uv-virtualenv/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::io;
use std::path::Path;

use platform_tags::PlatformError;
use thiserror::Error;

use platform_tags::PlatformError;
use uv_interpreter::{Interpreter, PythonEnvironment};

pub use crate::bare::create_bare_venv;
Expand All @@ -20,6 +20,8 @@ pub enum Error {
Platform(#[from] PlatformError),
#[error("Reserved key used for pyvenv.cfg: {0}")]
ReservedConfigKey(String),
#[error("Could not find a suitable Python executable for the virtual environment based on the interpreter: {0}")]
NotFound(String),
}

/// The value to use for the shell prompt when inside a virtual environment.
Expand Down

0 comments on commit ba69272

Please sign in to comment.