Skip to content

Commit

Permalink
Re-add
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Apr 20, 2024
1 parent 24fe0f6 commit 52f72b4
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 53 deletions.
110 changes: 90 additions & 20 deletions crates/uv-virtualenv/src/bare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ pub fn create_bare_venv(
// Create a `.gitignore` file to ignore all files in the venv.
fs::write(location.join(".gitignore"), "*")?;

// Per PEP 405, the Python `home` is the parent directory of the interpreter.
let python_home = base_python.parent().ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"The Python interpreter needs to have a parent directory",
)
})?;

// Different names for the python interpreter
fs::create_dir(&scripts)?;
let executable = scripts.join(format!("python{EXE_SUFFIX}"));
Expand Down Expand Up @@ -167,8 +175,10 @@ pub fn create_bare_venv(
{
// 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).
// 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()
Expand All @@ -179,27 +189,96 @@ pub fn create_bare_venv(
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!(),
};

// If `python.exe` doesn't exist, try the `venvlauncher.exe` shim.
let shim = interpreter
.stdlib()
.join("venv")
.join("scripts")
.join("nt")
.join(launcher);

// If the `venvlauncher.exe` shim doesn't exist, then on Conda at least, we
// can look for it next to the Python executable itself.
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);
fs_err::copy(shim, scripts.join(python_exe))?;
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.
fs_err::copy(
base_python.with_file_name(python_exe),
scripts.join(python_exe),
)?;

// 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 let Some(ext) = path.extension() {
if ext == "dll" || ext == "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 let Some(ext) = path.extension() {
if ext == "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());
Expand Down Expand Up @@ -242,18 +321,6 @@ pub fn create_bare_venv(
fs::write(scripts.join(name), activator)?;
}

// Per PEP 405, the Python `home` is the parent directory of the interpreter.
let python_home = base_python
.parent()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
"The Python interpreter needs to have a parent directory",
)
})?
.simplified_display()
.to_string();

// Validate extra_cfg
let reserved_keys = [
"home",
Expand All @@ -272,7 +339,10 @@ pub fn create_bare_venv(
}

let mut pyvenv_cfg_data: Vec<(String, String)> = vec![
("home".to_string(), python_home),
(
"home".to_string(),
python_home.simplified_display().to_string(),
),
(
"implementation".to_string(),
interpreter.markers().platform_python_implementation.clone(),
Expand Down
75 changes: 42 additions & 33 deletions scripts/check_embedded_python.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")

parser = argparse.ArgumentParser(description="Check a Python interpreter.")
parser = argparse.ArgumentParser(
description="Check an embedded Python interpreter."
)
parser.add_argument("--uv", help="Path to a uv binary.")
args = parser.parse_args()

Expand Down Expand Up @@ -49,40 +51,47 @@
env = os.environ.copy()
env["CONDA_PREFIX"] = ""
env["VIRTUAL_ENV"] = ""
subprocess.run(
[uv, "pip", "install", "pylint", "--verbose"],
cwd=temp_dir,
check=True,
env=env,
)

# Ensure that the package (`pylint`) is installed in the virtual environment.
logging.info("Checking that `pylint` is installed.")
code = subprocess.run(
[executable, "-c", "import pylint"],
cwd=temp_dir,
)
if code.returncode != 0:
raise Exception(
"The package `pylint` isn't installed in the virtual environment."
# Install, verify, and uninstall a few packages.
for package in ["pylint", "numpy"]:
# Install the package.
logging.info(
f"Installing the package `{package}` into the virtual environment..."
)
subprocess.run(
[uv, "pip", "install", package, "--verbose"],
cwd=temp_dir,
check=True,
env=env,
)

# Uninstall the package (`pylint`).
logging.info("Uninstalling the package `pylint`.")
subprocess.run(
[uv, "pip", "uninstall", "pylint", "--verbose"],
cwd=temp_dir,
check=True,
env=env,
)
# Ensure that the package is installed in the virtual environment.
logging.info(f"Checking that `{package}` is installed.")
code = subprocess.run(
[executable, "-c", f"import {package}"],
cwd=temp_dir,
)
if code.returncode != 0:
raise Exception(
f"The package `{package}` isn't installed in the virtual environment."
)

# Ensure that the package (`pylint`) isn't installed in the virtual environment.
logging.info("Checking that `pylint` isn't installed.")
code = subprocess.run(
[executable, "-m", "pip", "show", "pylint"],
cwd=temp_dir,
)
if code.returncode == 0:
raise Exception(
"The package `pylint` is installed in the virtual environment (but shouldn't be)."
# Uninstall the package.
logging.info(f"Uninstalling the package `{package}`.")
subprocess.run(
[uv, "pip", "uninstall", package, "--verbose"],
cwd=temp_dir,
check=True,
env=env,
)

# Ensure that the package isn't installed in the virtual environment.
logging.info(f"Checking that `{package}` isn't installed.")
code = subprocess.run(
[executable, "-m", "pip", "show", package],
cwd=temp_dir,
)
if code.returncode == 0:
raise Exception(
f"The package `{package}` is installed in the virtual environment (but shouldn't be)."
)

0 comments on commit 52f72b4

Please sign in to comment.