mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-09 04:00:34 +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"))?)
|
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".
|
/// Returns `true` if the environment is "relocatable".
|
||||||
pub fn relocatable(&self) -> bool {
|
pub fn relocatable(&self) -> bool {
|
||||||
self.cfg().is_ok_and(|cfg| cfg.is_relocatable())
|
self.cfg().is_ok_and(|cfg| cfg.is_relocatable())
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::{
|
use std::{
|
||||||
env, io,
|
env, io,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
@ -5,6 +6,7 @@ use std::{
|
||||||
|
|
||||||
use fs_err as fs;
|
use fs_err as fs;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use uv_pypi_types::Scheme;
|
use uv_pypi_types::Scheme;
|
||||||
use uv_static::EnvVars;
|
use uv_static::EnvVars;
|
||||||
|
|
||||||
|
@ -37,6 +39,8 @@ pub struct PyVenvConfiguration {
|
||||||
pub(crate) relocatable: bool,
|
pub(crate) relocatable: bool,
|
||||||
/// Was the virtual environment populated with seed packages?
|
/// Was the virtual environment populated with seed packages?
|
||||||
pub(crate) seed: bool,
|
pub(crate) seed: bool,
|
||||||
|
/// Should the virtual environment include system site packages?
|
||||||
|
pub(crate) include_system_side_packages: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -188,6 +192,7 @@ impl PyVenvConfiguration {
|
||||||
let mut uv = false;
|
let mut uv = false;
|
||||||
let mut relocatable = false;
|
let mut relocatable = false;
|
||||||
let mut seed = 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
|
// 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
|
// valid INI file, and is instead expected to be parsed by partitioning each line on the
|
||||||
|
@ -211,6 +216,9 @@ impl PyVenvConfiguration {
|
||||||
"seed" => {
|
"seed" => {
|
||||||
seed = value.trim().to_lowercase() == "true";
|
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,
|
uv,
|
||||||
relocatable,
|
relocatable,
|
||||||
seed,
|
seed,
|
||||||
|
include_system_side_packages,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,4 +251,119 @@ impl PyVenvConfiguration {
|
||||||
pub fn is_seed(&self) -> bool {
|
pub fn is_seed(&self) -> bool {
|
||||||
self.seed
|
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(())
|
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
|
/// Return the [`Interpreter`] to use for the cached environment, based on a given
|
||||||
/// [`Interpreter`].
|
/// [`Interpreter`].
|
||||||
///
|
///
|
||||||
|
|
|
@ -25,8 +25,9 @@ use uv_fs::{PythonExt, Simplified};
|
||||||
use uv_installer::{SatisfiesResult, SitePackages};
|
use uv_installer::{SatisfiesResult, SitePackages};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_python::{
|
use uv_python::{
|
||||||
EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation,
|
EnvironmentPreference, Interpreter, PyVenvConfiguration, PythonDownloads, PythonEnvironment,
|
||||||
PythonPreference, PythonRequest, PythonVersionFile, VersionFileDiscoveryOptions,
|
PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile,
|
||||||
|
VersionFileDiscoveryOptions,
|
||||||
};
|
};
|
||||||
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
use uv_requirements::{RequirementsSource, RequirementsSpecification};
|
||||||
use uv_resolver::Lock;
|
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(\"{}\")",
|
"import site; site.addsitedir(\"{}\")",
|
||||||
site_packages.escape_for_python()
|
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`.
|
// Cast from `CachedEnvironment` to `PythonEnvironment`.
|
||||||
|
|
|
@ -801,6 +801,7 @@ async fn get_or_create_environment(
|
||||||
|
|
||||||
// Clear any existing overlay.
|
// Clear any existing overlay.
|
||||||
environment.clear_overlay()?;
|
environment.clear_overlay()?;
|
||||||
|
environment.clear_system_site_packages()?;
|
||||||
|
|
||||||
Ok((from, environment.into()))
|
Ok((from, environment.into()))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue