Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
LinkErrorReporting, PlatformState, ProjectEnvironment, ProjectEnvironmentPolicy, ProjectError,
ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython,
init_script_python_requirement,
LinkErrorReporting, PlatformState, ProjectEnvironment, ProjectEnvironmentPolicy,
ProjectEnvironmentTarget, ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState,
WorkspacePython, init_script_python_requirement,
};
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, UvError, project};
Expand Down Expand Up @@ -320,7 +320,7 @@ pub(crate) async fn add(
)
.await?;
let interpreter = ProjectInterpreter::discover(
project.workspace(),
ProjectEnvironmentTarget::from(project.workspace()),
&defaulted_groups,
workspace_python,
&client_builder,
Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/project/audit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use crate::commands::pip::resolution_markers;
use crate::commands::project::lock::{LockMode, LockOperation};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
ProjectEnvironmentPolicy, ProjectInterpreter, ScriptInterpreter, UniversalState,
WorkspacePython,
ProjectEnvironmentPolicy, ProjectEnvironmentTarget, ProjectInterpreter, ScriptInterpreter,
UniversalState, WorkspacePython,
};
use crate::commands::reporters::AuditReporter;
use crate::printer::Printer;
Expand Down Expand Up @@ -157,7 +157,7 @@ pub(crate) async fn audit(
)
.await?;
ProjectInterpreter::discover(
workspace,
ProjectEnvironmentTarget::from(workspace),
&groups,
workspace_python,
&client_builder,
Expand Down
8 changes: 4 additions & 4 deletions crates/uv/src/commands/project/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
LinkErrorReporting, ProjectEnvironment, ProjectEnvironmentPolicy, ProjectInterpreter,
PythonRequirementSource, ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
validate_python_requirement,
LinkErrorReporting, ProjectEnvironment, ProjectEnvironmentPolicy, ProjectEnvironmentTarget,
ProjectInterpreter, PythonRequirementSource, ScriptEnvironment, ScriptInterpreter,
UniversalState, WorkspacePython, validate_python_requirement,
};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::{ExitStatus, UvError, project};
Expand Down Expand Up @@ -546,7 +546,7 @@ pub(crate) async fn check(
.await?;
Some(
ProjectInterpreter::discover(
project.workspace(),
ProjectEnvironmentTarget::from(project.workspace()),
&groups,
workspace_python,
&client_builder,
Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ use crate::commands::project::lock::{LockMode, LockOperation};
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::lockfile::FrozenWorkspace;
use crate::commands::project::{
ProjectEnvironmentPolicy, ProjectInterpreter, ScriptInterpreter, UniversalState,
WorkspacePython, detect_conflicts,
ProjectEnvironmentPolicy, ProjectEnvironmentTarget, ProjectInterpreter, ScriptInterpreter,
UniversalState, WorkspacePython, detect_conflicts,
};
use crate::commands::{ExitStatus, OutputWriter, UvError};
use crate::printer::Printer;
Expand Down Expand Up @@ -313,7 +313,7 @@ pub(crate) async fn export(
)
.await?;
ProjectInterpreter::discover(
project.workspace(),
ProjectEnvironmentTarget::from(project.workspace()),
&interpreter_groups,
workspace_python,
&client_builder,
Expand Down
8 changes: 4 additions & 4 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ use crate::commands::locked_requirements::{LockedRequirements, read_lock_require
use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, SummaryResolveLogger};
use crate::commands::project::lock_target::{LockTarget, find_lock_format_error};
use crate::commands::project::{
MissingLockfileSource, ProjectEnvironmentPolicy, ProjectError, ProjectInterpreter,
ScriptInterpreter, UniversalState, WorkspacePython, init_script_python_requirement,
script_extra_build_requires,
MissingLockfileSource, ProjectEnvironmentPolicy, ProjectEnvironmentTarget, ProjectError,
ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython,
init_script_python_requirement, script_extra_build_requires,
};
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, UvError, pip};
Expand Down Expand Up @@ -165,7 +165,7 @@ pub(crate) async fn lock(
)
.await?;
ProjectInterpreter::discover(
workspace,
ProjectEnvironmentTarget::from(workspace),
&groups,
workspace_python,
&client_builder,
Expand Down
99 changes: 75 additions & 24 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1426,15 +1426,16 @@ pub(crate) fn is_centralized_environment_reference(path: &Path, cache: &Cache) -
.is_ok_and(|target| is_centralized_environment_path(&target, cache))
}

/// Return the centralized environment path for a given workspace and interpreter.
/// Return the centralized environment path for a project and interpreter.
pub(crate) fn centralized_environment_root(
workspace: &Workspace,
target: ProjectEnvironmentTarget<'_>,
interpreter: &Interpreter,
upgradeable: bool,
cache: &Cache,
) -> PathBuf {
let workspace_path = fs_err::canonicalize(workspace.install_path())
.unwrap_or_else(|_| workspace.install_path().clone());
let install_path = target.install_path();
let workspace_path =
fs_err::canonicalize(install_path).unwrap_or_else(|_| install_path.to_path_buf());
let interpreter_key = interpreter.key();
// Use the workspace path to isolate projects and the interpreter key to maximize intra-project
// environment re-use while avoiding clashes with incompatible environments. Ignoring the patch
Expand All @@ -1454,11 +1455,9 @@ pub(crate) fn centralized_environment_root(
interpreter.python_version().clone(),
)
};
let name = workspace
.pyproject_toml()
.project
.as_ref()
.and_then(|project| cache_name(project.name.as_ref(), Some(100)))
let name = target
.project_name()
.and_then(|name| cache_name(name.as_ref(), Some(100)))
.or_else(|| {
workspace_path
.file_name()
Expand Down Expand Up @@ -1555,6 +1554,46 @@ pub(crate) fn update_project_environment_link(
false
}

/// The project information needed to discover and create an environment.
#[derive(Clone, Copy)]
pub(crate) enum ProjectEnvironmentTarget<'a> {
Workspace(&'a Workspace),
Lockfile { root: &'a Path, lock: &'a Lock },
}

impl<'a> From<&'a Workspace> for ProjectEnvironmentTarget<'a> {
fn from(workspace: &'a Workspace) -> Self {
Self::Workspace(workspace)
}
}

impl<'a> ProjectEnvironmentTarget<'a> {
fn install_path(self) -> &'a Path {
match self {
Self::Workspace(workspace) => workspace.install_path(),
Self::Lockfile { root, .. } => root,
}
}

fn project_name(self) -> Option<&'a PackageName> {
match self {
Self::Workspace(workspace) => workspace
.pyproject_toml()
.project
.as_ref()
.map(|project| &project.name),
Self::Lockfile { lock, .. } => lock.root().map(uv_lock::Package::name),
}
}

fn workspace(self) -> Option<&'a Workspace> {
match self {
Self::Workspace(workspace) => Some(workspace),
Self::Lockfile { .. } => None,
}
}
}

/// An interpreter suitable for the project.
#[derive(Debug)]
#[expect(clippy::large_enum_variant)]
Expand Down Expand Up @@ -1593,9 +1632,9 @@ impl ProjectInterpreter {
)
}

/// Discover the interpreter to use in the current [`Workspace`].
/// Discover an interpreter for a workspace or frozen lockfile.
pub(crate) async fn discover(
workspace: &Workspace,
target: ProjectEnvironmentTarget<'_>,
groups: &DependencyGroupsWithDefaults,
workspace_python: WorkspacePython,
client_builder: &BaseClientBuilder<'_>,
Expand All @@ -1617,7 +1656,8 @@ impl ProjectInterpreter {
.as_ref()
.map(|requirement| &requirement.requires_python);

let environment_selection = workspace.environment_selection(active);
let environment_selection =
ProjectEnvironmentSelection::from_install_path(target.install_path(), active);
let centralized = centralized_environments_enabled(&environment_selection, cache);
let upgradeable = python_request
.as_ref()
Expand All @@ -1626,7 +1666,7 @@ impl ProjectInterpreter {
// Prefer `.venv`'s interpreter to keep its compatible cached environment selected; derive
// the cache root instead of trusting the link target.
if centralized {
let project_environment_path = workspace.install_path().join(".venv");
let project_environment_path = target.install_path().join(".venv");
if let Ok(candidate) = PythonEnvironment::from_root(
read_environment_path_file(&project_environment_path)
.ok()
Expand All @@ -1635,7 +1675,7 @@ impl ProjectInterpreter {
cache,
) {
let root = centralized_environment_root(
workspace,
target,
candidate.interpreter(),
upgradeable,
cache,
Expand All @@ -1656,7 +1696,7 @@ impl ProjectInterpreter {
} else {
let project_environment_path = environment_selection
.explicit_path()
.map_or_else(|| workspace.install_path().join(".venv"), Path::to_path_buf);
.map_or_else(|| target.install_path().join(".venv"), Path::to_path_buf);
// TODO(tk): Revisit after PEP 832.
// A centralized path file is not a local environment; let initialization replace it.
if !(environment_selection.is_default()
Expand Down Expand Up @@ -1697,7 +1737,7 @@ impl ProjectInterpreter {

if centralized {
let root =
centralized_environment_root(workspace, python.interpreter(), upgradeable, cache);
centralized_environment_root(target, python.interpreter(), upgradeable, cache);
if let Some(environment) = discover_project_environment(
&root,
python_request.as_ref(),
Expand Down Expand Up @@ -1738,7 +1778,7 @@ impl ProjectInterpreter {
if let Some(requirement) = requirement.as_ref() {
let requirement_source = match &requirement.source {
WorkspacePythonRequirementSource::Workspace => {
PythonRequirementSource::Workspace(Some(workspace), groups)
PythonRequirementSource::Workspace(target.workspace(), groups)
}
WorkspacePythonRequirementSource::Lockfile { locked, groups } => {
PythonRequirementSource::Lockfile { locked, groups }
Expand Down Expand Up @@ -1845,16 +1885,16 @@ impl WorkspacePython {
/// Determine the Python request and requirement from a frozen lockfile.
async fn from_lockfile(
python_request: Option<PythonRequest>,
workspace: &Workspace,
target: InstallTarget<'_>,
groups: &DependencyGroupsWithDefaults,
project_dir: &Path,
config_discovery: ConfigDiscovery,
) -> Result<Self, ProjectError> {
Self::from_requirements(
python_request,
Some(workspace.install_path()),
Some(target.install_path()),
Some(find_lockfile_requires_python(target, groups)?),
workspace.install_path(),
project_dir,
config_discovery,
)
.await
Expand Down Expand Up @@ -2115,8 +2155,14 @@ impl ProjectEnvironment {
.ok();

let workspace_python = if let Some(target) = frozen_target {
WorkspacePython::from_lockfile(python, workspace, target, groups, config_discovery)
.await?
WorkspacePython::from_lockfile(
python,
target,
groups,
workspace.install_path(),
config_discovery,
)
.await?
} else {
WorkspacePython::from_request(
python,
Expand All @@ -2133,7 +2179,7 @@ impl ProjectEnvironment {
.is_none_or(|request| !request.includes_patch());

match ProjectInterpreter::discover(
workspace,
ProjectEnvironmentTarget::from(workspace),
groups,
workspace_python,
client_builder,
Expand Down Expand Up @@ -2163,7 +2209,12 @@ impl ProjectEnvironment {
// Otherwise, create a virtual environment with the discovered interpreter.
ProjectInterpreter::Interpreter(interpreter) => {
let root = if centralized {
centralized_environment_root(workspace, &interpreter, upgradeable, cache)
centralized_environment_root(
ProjectEnvironmentTarget::from(workspace),
&interpreter,
upgradeable,
cache,
)
} else {
environment_selection
.explicit_path()
Expand Down
6 changes: 3 additions & 3 deletions crates/uv/src/commands/project/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
LinkErrorReporting, ProjectEnvironment, ProjectEnvironmentPolicy, ProjectError,
ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython,
LinkErrorReporting, ProjectEnvironment, ProjectEnvironmentPolicy, ProjectEnvironmentTarget,
ProjectError, ProjectInterpreter, ScriptInterpreter, UniversalState, WorkspacePython,
};
use crate::commands::{ExitStatus, UvError, project};
use crate::printer::Printer;
Expand Down Expand Up @@ -253,7 +253,7 @@ pub(crate) async fn remove(
)
.await?;
let interpreter = ProjectInterpreter::discover(
project.workspace(),
ProjectEnvironmentTarget::from(project.workspace()),
&groups,
workspace_python,
&client_builder,
Expand Down
Loading
Loading