Revert "Includes sys.prefix in cached environment keys to avoid --with collisions across projects (#14403)"

This reverts commit ec54dce919.
This commit is contained in:
Charlie Marsh 2025-07-03 16:22:43 -04:00
parent 537082c19e
commit 0fd8580e20
3 changed files with 15 additions and 33 deletions

View file

@ -174,7 +174,7 @@ impl PythonEnvironment {
/// N.B. This function also works for system Python environments and users depend on this. /// N.B. This function also works for system Python environments and users depend on this.
pub fn from_root(root: impl AsRef<Path>, cache: &Cache) -> Result<Self, Error> { pub fn from_root(root: impl AsRef<Path>, cache: &Cache) -> Result<Self, Error> {
debug!( debug!(
"Checking for Python environment at: {}", "Checking for Python environment at: `{}`",
root.as_ref().user_display() root.as_ref().user_display()
); );
match root.as_ref().try_exists() { match root.as_ref().try_exists() {

View file

@ -107,15 +107,13 @@ impl CachedEnvironment {
printer: Printer, printer: Printer,
preview: PreviewMode, preview: PreviewMode,
) -> Result<Self, ProjectError> { ) -> Result<Self, ProjectError> {
// Resolve the "base" interpreter, which resolves to an underlying parent interpreter if the let interpreter = Self::base_interpreter(interpreter, cache)?;
// given interpreter is a virtual environment.
let base_interpreter = Self::base_interpreter(interpreter, cache)?;
// Resolve the requirements with the interpreter. // Resolve the requirements with the interpreter.
let resolution = Resolution::from( let resolution = Resolution::from(
resolve_environment( resolve_environment(
spec, spec,
&base_interpreter, &interpreter,
build_constraints.clone(), build_constraints.clone(),
&settings.resolver, &settings.resolver,
network_settings, network_settings,
@ -143,29 +141,20 @@ impl CachedEnvironment {
// Use the canonicalized base interpreter path since that's the interpreter we performed the // Use the canonicalized base interpreter path since that's the interpreter we performed the
// resolution with and the interpreter the environment will be created with. // resolution with and the interpreter the environment will be created with.
// //
// We also include the canonicalized `sys.prefix` of the non-base interpreter, that is, the // We cache environments independent of the environment they'd be layered on top of. The
// virtual environment's path. Originally, we shared cached environments independent of the // assumption is such that the environment will _not_ be modified by the user or uv;
// environment they'd be layered on top of. However, this causes collisions as the overlay // otherwise, we risk cache poisoning. For example, if we were to write a `.pth` file to
// `.pth` file can be overridden by another instance of uv. Including this element in the key // the cached environment, it would be shared across all projects that use the same
// avoids this problem at the cost of creating separate cached environments for identical // interpreter and the same cached dependencies.
// `--with` invocations across projects. We use `sys.prefix` rather than `sys.executable` so
// we can canonicalize it without invalidating the purpose of the element — it'd probably be
// safe to just use the absolute `sys.executable` as well.
//
// TODO(zanieb): Since we're not sharing these environmments across projects, we should move
// [`CachedEvnvironment::set_overlay`] etc. here since the values there should be constant
// now.
// //
// TODO(zanieb): We should include the version of the base interpreter in the hash, so if // TODO(zanieb): We should include the version of the base interpreter in the hash, so if
// the interpreter at the canonicalized path changes versions we construct a new // the interpreter at the canonicalized path changes versions we construct a new
// environment. // environment.
let environment_hash = cache_digest(&( let interpreter_hash =
&canonicalize_executable(base_interpreter.sys_executable())?, cache_digest(&canonicalize_executable(interpreter.sys_executable())?);
&interpreter.sys_prefix().canonicalize()?,
));
// Search in the content-addressed cache. // Search in the content-addressed cache.
let cache_entry = cache.entry(CacheBucket::Environments, environment_hash, resolution_hash); let cache_entry = cache.entry(CacheBucket::Environments, interpreter_hash, resolution_hash);
if cache.refresh().is_none() { if cache.refresh().is_none() {
if let Ok(root) = cache.resolve_link(cache_entry.path()) { if let Ok(root) = cache.resolve_link(cache_entry.path()) {
@ -179,7 +168,7 @@ impl CachedEnvironment {
let temp_dir = cache.venv_dir()?; let temp_dir = cache.venv_dir()?;
let venv = uv_virtualenv::create_venv( let venv = uv_virtualenv::create_venv(
temp_dir.path(), temp_dir.path(),
base_interpreter, interpreter,
uv_virtualenv::Prompt::None, uv_virtualenv::Prompt::None,
false, false,
false, false,

View file

@ -1302,7 +1302,6 @@ fn run_with_pyvenv_cfg_file() -> Result<()> {
uv = [UV_VERSION] uv = [UV_VERSION]
version_info = 3.12.[X] version_info = 3.12.[X]
include-system-site-packages = false include-system-site-packages = false
relocatable = true
extends-environment = [PARENT_VENV] extends-environment = [PARENT_VENV]
@ -4778,7 +4777,6 @@ fn run_groups_include_requires_python() -> Result<()> {
baz = ["iniconfig"] baz = ["iniconfig"]
dev = ["sniffio", {include-group = "foo"}, {include-group = "baz"}] dev = ["sniffio", {include-group = "foo"}, {include-group = "baz"}]
[tool.uv.dependency-groups] [tool.uv.dependency-groups]
foo = {requires-python="<3.13"} foo = {requires-python="<3.13"}
bar = {requires-python=">=3.13"} bar = {requires-python=">=3.13"}
@ -4923,8 +4921,8 @@ fn run_repeated() -> Result<()> {
Resolved 1 package in [TIME] Resolved 1 package in [TIME]
"###); "###);
// Re-running as a tool does require reinstalling `typing-extensions`, since the base venv is // Re-running as a tool doesn't require reinstalling `typing-extensions`, since the environment
// different. // is cached.
uv_snapshot!( uv_snapshot!(
context.filters(), context.filters(),
context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#" context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#"
@ -4934,8 +4932,6 @@ fn run_repeated() -> Result<()> {
----- stderr ----- ----- stderr -----
Resolved 1 package in [TIME] Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ typing-extensions==4.10.0
Traceback (most recent call last): Traceback (most recent call last):
File "<string>", line 1, in <module> File "<string>", line 1, in <module>
import typing_extensions; import iniconfig import typing_extensions; import iniconfig
@ -4982,8 +4978,7 @@ fn run_without_overlay() -> Result<()> {
+ typing-extensions==4.10.0 + typing-extensions==4.10.0
"###); "###);
// Import `iniconfig` in the context of a `tool run` command, which should fail. Note that // Import `iniconfig` in the context of a `tool run` command, which should fail.
// typing-extensions gets installed again, because the venv is not shared.
uv_snapshot!( uv_snapshot!(
context.filters(), context.filters(),
context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#" context.tool_run().arg("--with").arg("typing-extensions").arg("python").arg("-c").arg("import typing_extensions; import iniconfig"), @r#"
@ -4993,8 +4988,6 @@ fn run_without_overlay() -> Result<()> {
----- stderr ----- ----- stderr -----
Resolved 1 package in [TIME] Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ typing-extensions==4.10.0
Traceback (most recent call last): Traceback (most recent call last):
File "<string>", line 1, in <module> File "<string>", line 1, in <module>
import typing_extensions; import iniconfig import typing_extensions; import iniconfig