mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-03 18:38:21 +00:00
Respect include-system-site-packages
in layered environments (#11873)
## Summary We use a similar strategy to the ephemeral overlay: set `include-system-site-packages` in the `pyvenv.cfg`, and clear it whenever we access a new environment. Closes https://github.com/astral-sh/uv/issues/11829. ## Test Plan Difficult to test because we don't really have support for system packages in our test infrastructure. But... ``` > uv venv --system-site-packages > ['', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python313.zip', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13/lib-dynload', '/Users/crmarsh/.cache/uv/archive-v0/AhKcORkaCdbBl31VweRtG/lib/python3.13/site-packages', '/Users/crmarsh/workspace/uv/foo/.venv/lib/python3.13/site-packages', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13/site-packages'] ``` ``` > uv venv > ['', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python313.zip', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13', '/Users/crmarsh/.local/share/uv/python/cpython-3.13.0-macos-aarch64-none/lib/python3.13/lib-dynload', '/Users/crmarsh/.cache/uv/archive-v0/AhKcORkaCdbBl31VweRtG/lib/python3.13/site-packages', '/Users/crmarsh/workspace/uv/foo/.venv/lib/python3.13/site-packages'] ```
This commit is contained in:
parent
3017b82ecc
commit
3398cb1902
5 changed files with 169 additions and 2 deletions
|
@ -270,6 +270,16 @@ impl PythonEnvironment {
|
|||
Ok(PyVenvConfiguration::parse(self.0.root.join("pyvenv.cfg"))?)
|
||||
}
|
||||
|
||||
/// Set a key-value pair in the `pyvenv.cfg` file.
|
||||
pub fn set_pyvenv_cfg(&self, key: &str, value: &str) -> Result<(), Error> {
|
||||
let content = fs_err::read_to_string(self.0.root.join("pyvenv.cfg"))?;
|
||||
fs_err::write(
|
||||
self.0.root.join("pyvenv.cfg"),
|
||||
PyVenvConfiguration::set(&content, key, value),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if the environment is "relocatable".
|
||||
pub fn relocatable(&self) -> bool {
|
||||
self.cfg().is_ok_and(|cfg| cfg.is_relocatable())
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::borrow::Cow;
|
||||
use std::{
|
||||
env, io,
|
||||
path::{Path, PathBuf},
|
||||
|
@ -5,6 +6,7 @@ use std::{
|
|||
|
||||
use fs_err as fs;
|
||||
use thiserror::Error;
|
||||
|
||||
use uv_pypi_types::Scheme;
|
||||
use uv_static::EnvVars;
|
||||
|
||||
|
@ -37,6 +39,8 @@ pub struct PyVenvConfiguration {
|
|||
pub(crate) relocatable: bool,
|
||||
/// Was the virtual environment populated with seed packages?
|
||||
pub(crate) seed: bool,
|
||||
/// Should the virtual environment include system site packages?
|
||||
pub(crate) include_system_side_packages: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
@ -188,6 +192,7 @@ impl PyVenvConfiguration {
|
|||
let mut uv = false;
|
||||
let mut relocatable = false;
|
||||
let mut seed = false;
|
||||
let mut include_system_side_packages = false;
|
||||
|
||||
// Per https://snarky.ca/how-virtual-environments-work/, the `pyvenv.cfg` file is not a
|
||||
// valid INI file, and is instead expected to be parsed by partitioning each line on the
|
||||
|
@ -211,6 +216,9 @@ impl PyVenvConfiguration {
|
|||
"seed" => {
|
||||
seed = value.trim().to_lowercase() == "true";
|
||||
}
|
||||
"include-system-site-packages" => {
|
||||
include_system_side_packages = value.trim().to_lowercase() == "true";
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -220,6 +228,7 @@ impl PyVenvConfiguration {
|
|||
uv,
|
||||
relocatable,
|
||||
seed,
|
||||
include_system_side_packages,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -242,4 +251,119 @@ impl PyVenvConfiguration {
|
|||
pub fn is_seed(&self) -> bool {
|
||||
self.seed
|
||||
}
|
||||
|
||||
/// Returns true if the virtual environment should include system site packages.
|
||||
pub fn include_system_side_packages(&self) -> bool {
|
||||
self.include_system_side_packages
|
||||
}
|
||||
|
||||
/// Set the key-value pair in the `pyvenv.cfg` file.
|
||||
pub fn set(content: &str, key: &str, value: &str) -> String {
|
||||
let mut lines = content.lines().map(Cow::Borrowed).collect::<Vec<_>>();
|
||||
let mut found = false;
|
||||
for line in &mut lines {
|
||||
if let Some((lhs, _)) = line.split_once('=') {
|
||||
if lhs.trim() == key {
|
||||
*line = Cow::Owned(format!("{key} = {value}"));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
lines.push(Cow::Owned(format!("{key} = {value}")));
|
||||
}
|
||||
if lines.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{}\n", lines.join("\n"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use indoc::indoc;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_set_existing_key() {
|
||||
let content = indoc! {"
|
||||
home = /path/to/python
|
||||
version = 3.8.0
|
||||
include-system-site-packages = false
|
||||
"};
|
||||
let result = PyVenvConfiguration::set(content, "version", "3.9.0");
|
||||
assert_eq!(
|
||||
result,
|
||||
indoc! {"
|
||||
home = /path/to/python
|
||||
version = 3.9.0
|
||||
include-system-site-packages = false
|
||||
"}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_new_key() {
|
||||
let content = indoc! {"
|
||||
home = /path/to/python
|
||||
version = 3.8.0
|
||||
"};
|
||||
let result = PyVenvConfiguration::set(content, "include-system-site-packages", "false");
|
||||
assert_eq!(
|
||||
result,
|
||||
indoc! {"
|
||||
home = /path/to/python
|
||||
version = 3.8.0
|
||||
include-system-site-packages = false
|
||||
"}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_key_no_spaces() {
|
||||
let content = indoc! {"
|
||||
home=/path/to/python
|
||||
version=3.8.0
|
||||
"};
|
||||
let result = PyVenvConfiguration::set(content, "include-system-site-packages", "false");
|
||||
assert_eq!(
|
||||
result,
|
||||
indoc! {"
|
||||
home=/path/to/python
|
||||
version=3.8.0
|
||||
include-system-site-packages = false
|
||||
"}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_key_prefix() {
|
||||
let content = indoc! {"
|
||||
home = /path/to/python
|
||||
home_dir = /other/path
|
||||
"};
|
||||
let result = PyVenvConfiguration::set(content, "home", "new/path");
|
||||
assert_eq!(
|
||||
result,
|
||||
indoc! {"
|
||||
home = new/path
|
||||
home_dir = /other/path
|
||||
"}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_empty_content() {
|
||||
let content = "";
|
||||
let result = PyVenvConfiguration::set(content, "version", "3.9.0");
|
||||
assert_eq!(
|
||||
result,
|
||||
indoc! {"
|
||||
version = 3.9.0
|
||||
"}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -150,6 +150,22 @@ impl CachedEnvironment {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable system site packages for a Python environment.
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub(crate) fn set_system_site_packages(&self) -> Result<(), ProjectError> {
|
||||
self.0
|
||||
.set_pyvenv_cfg("include-system-site-packages", "true")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Disable system site packages for a Python environment.
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub(crate) fn clear_system_site_packages(&self) -> Result<(), ProjectError> {
|
||||
self.0
|
||||
.set_pyvenv_cfg("include-system-site-packages", "false")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the [`Interpreter`] to use for the cached environment, based on a given
|
||||
/// [`Interpreter`].
|
||||
///
|
||||
|
|
|
@ -25,8 +25,9 @@ use uv_fs::{PythonExt, Simplified};
|
|||
use uv_installer::{SatisfiesResult, SitePackages};
|
||||
use uv_normalize::PackageName;
|
||||
use uv_python::{
|
||||
EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation,
|
||||
PythonPreference, PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions,
|
||||
EnvironmentPreference, Interpreter, PyVenvConfiguration, PythonDownloads, PythonEnvironment,
|
||||
PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile,
|
||||
VersionFileDiscoveryOptions,
|
||||
};
|
||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||
use uv_resolver::Lock;
|
||||
|
@ -923,6 +924,21 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
|
|||
"import site; site.addsitedir(\"{}\")",
|
||||
site_packages.escape_for_python()
|
||||
))?;
|
||||
|
||||
// If `--system-site-packages` is enabled, add the system site packages to the ephemeral
|
||||
// environment.
|
||||
if base_interpreter
|
||||
.is_virtualenv()
|
||||
.then(|| {
|
||||
PyVenvConfiguration::parse(base_interpreter.sys_prefix().join("pyvenv.cfg"))
|
||||
.is_ok_and(|cfg| cfg.include_system_side_packages())
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
ephemeral_env.set_system_site_packages()?;
|
||||
} else {
|
||||
ephemeral_env.clear_system_site_packages()?;
|
||||
}
|
||||
}
|
||||
|
||||
// Cast from `CachedEnvironment` to `PythonEnvironment`.
|
||||
|
|
|
@ -801,6 +801,7 @@ async fn get_or_create_environment(
|
|||
|
||||
// Clear any existing overlay.
|
||||
environment.clear_overlay()?;
|
||||
environment.clear_system_site_packages()?;
|
||||
|
||||
Ok((from, environment.into()))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue