mirror of
https://github.com/astral-sh/uv.git
synced 2025-07-07 21:35:00 +00:00
Fallback to interpreter discovery in uv run
(#4549)
## Summary This PR modifies `uv run` to fallback to discovering an interpreter (e.g., a local `.venv`) if the command is run outside of a workspace. `uv run --isolated` continues to completely skip workspace _and_ interpreter discovering, only installing whatever's provided with `--with`. The next step here is adding some ergonomic controls for enabling this behavior even if your project is technically in a workspace (i.e., you have a `pyproject.toml` but aren't using the Project APIs and don't want locking etc.). I could imagine a setting in `pyproject.toml` that's also exposed on the command-line. Something like: `managed = false` or `project = false`. See: https://github.com/astral-sh/uv/issues/3836.
This commit is contained in:
parent
c9657b0015
commit
0fe5eacdba
1 changed files with 102 additions and 58 deletions
|
@ -10,7 +10,7 @@ use uv_cache::Cache;
|
||||||
use uv_cli::ExternalCommand;
|
use uv_cli::ExternalCommand;
|
||||||
use uv_client::{BaseClientBuilder, Connectivity};
|
use uv_client::{BaseClientBuilder, Connectivity};
|
||||||
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode};
|
use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode};
|
||||||
use uv_distribution::{ProjectWorkspace, Workspace};
|
use uv_distribution::{ProjectWorkspace, Workspace, WorkspaceError};
|
||||||
use uv_normalize::PackageName;
|
use uv_normalize::PackageName;
|
||||||
use uv_requirements::RequirementsSource;
|
use uv_requirements::RequirementsSource;
|
||||||
use uv_toolchain::{
|
use uv_toolchain::{
|
||||||
|
@ -46,68 +46,112 @@ pub(crate) async fn run(
|
||||||
warn_user!("`uv run` is experimental and may change without warning.");
|
warn_user!("`uv run` is experimental and may change without warning.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discover and sync the project.
|
// Discover and sync the base environment.
|
||||||
let project_env = if isolated {
|
let base_env = if isolated {
|
||||||
// package is `None`, isolated and package are marked as conflicting in clap.
|
// package is `None`, isolated and package are marked as conflicting in clap.
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
debug!("Syncing project environment.");
|
|
||||||
|
|
||||||
let project = if let Some(package) = package {
|
let project = if let Some(package) = package {
|
||||||
// We need a workspace, but we don't need to have a current package, we can be e.g. in
|
// We need a workspace, but we don't need to have a current package, we can be e.g. in
|
||||||
// the root of a virtual workspace and then switch into the selected package.
|
// the root of a virtual workspace and then switch into the selected package.
|
||||||
Workspace::discover(&std::env::current_dir()?, None)
|
Some(
|
||||||
.await?
|
Workspace::discover(&std::env::current_dir()?, None)
|
||||||
.with_current_project(package.clone())
|
.await?
|
||||||
.with_context(|| format!("Package `{package}` not found in workspace"))?
|
.with_current_project(package.clone())
|
||||||
|
.with_context(|| format!("Package `{package}` not found in workspace"))?,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
ProjectWorkspace::discover(&std::env::current_dir()?, None).await?
|
match ProjectWorkspace::discover(&std::env::current_dir()?, None).await {
|
||||||
|
Ok(project) => Some(project),
|
||||||
|
Err(WorkspaceError::MissingPyprojectToml) => None,
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let venv = project::init_environment(
|
|
||||||
project.workspace(),
|
|
||||||
python.as_deref().map(ToolchainRequest::parse),
|
|
||||||
toolchain_preference,
|
|
||||||
connectivity,
|
|
||||||
native_tls,
|
|
||||||
cache,
|
|
||||||
printer,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Lock and sync the environment.
|
let venv = if let Some(project) = project {
|
||||||
let lock = project::lock::do_lock(
|
debug!(
|
||||||
project.workspace(),
|
"Discovered project `{}` at: {}",
|
||||||
venv.interpreter(),
|
project.project_name(),
|
||||||
settings.as_ref().into(),
|
project.workspace().root().display()
|
||||||
preview,
|
);
|
||||||
connectivity,
|
|
||||||
concurrency,
|
let venv = project::init_environment(
|
||||||
native_tls,
|
project.workspace(),
|
||||||
cache,
|
python.as_deref().map(ToolchainRequest::parse),
|
||||||
printer,
|
toolchain_preference,
|
||||||
)
|
connectivity,
|
||||||
.await?;
|
native_tls,
|
||||||
project::sync::do_sync(
|
cache,
|
||||||
project.project_name(),
|
printer,
|
||||||
project.workspace().root(),
|
)
|
||||||
&venv,
|
.await?;
|
||||||
&lock,
|
|
||||||
extras,
|
// Lock and sync the environment.
|
||||||
dev,
|
let lock = project::lock::do_lock(
|
||||||
Modifications::Sufficient,
|
project.workspace(),
|
||||||
settings.as_ref().into(),
|
venv.interpreter(),
|
||||||
preview,
|
settings.as_ref().into(),
|
||||||
connectivity,
|
preview,
|
||||||
concurrency,
|
connectivity,
|
||||||
native_tls,
|
concurrency,
|
||||||
cache,
|
native_tls,
|
||||||
printer,
|
cache,
|
||||||
)
|
printer,
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
project::sync::do_sync(
|
||||||
|
project.project_name(),
|
||||||
|
project.workspace().root(),
|
||||||
|
&venv,
|
||||||
|
&lock,
|
||||||
|
extras,
|
||||||
|
dev,
|
||||||
|
Modifications::Sufficient,
|
||||||
|
settings.as_ref().into(),
|
||||||
|
preview,
|
||||||
|
connectivity,
|
||||||
|
concurrency,
|
||||||
|
native_tls,
|
||||||
|
cache,
|
||||||
|
printer,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
venv
|
||||||
|
} else {
|
||||||
|
debug!("No project found; searching for Python interpreter");
|
||||||
|
|
||||||
|
let client_builder = BaseClientBuilder::new()
|
||||||
|
.connectivity(connectivity)
|
||||||
|
.native_tls(native_tls);
|
||||||
|
|
||||||
|
let toolchain = Toolchain::find_or_fetch(
|
||||||
|
python.as_deref().map(ToolchainRequest::parse),
|
||||||
|
// No opt-in is required for system environments, since we are not mutating it.
|
||||||
|
EnvironmentPreference::Any,
|
||||||
|
toolchain_preference,
|
||||||
|
client_builder,
|
||||||
|
cache,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Creating a `PythonEnvironment` from a `find_or_fetch` is generally discouraged, since
|
||||||
|
// we may end up modifying a managed toolchain. However, the environment here is
|
||||||
|
// read-only.
|
||||||
|
PythonEnvironment::from_toolchain(toolchain)
|
||||||
|
};
|
||||||
|
|
||||||
Some(venv)
|
Some(venv)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(base_env) = &base_env {
|
||||||
|
debug!(
|
||||||
|
"Using Python {} interpreter at: {}",
|
||||||
|
base_env.interpreter().python_version(),
|
||||||
|
base_env.interpreter().sys_executable().display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// If necessary, create an environment for the ephemeral requirements.
|
// If necessary, create an environment for the ephemeral requirements.
|
||||||
let temp_dir;
|
let temp_dir;
|
||||||
let ephemeral_env = if requirements.is_empty() {
|
let ephemeral_env = if requirements.is_empty() {
|
||||||
|
@ -115,14 +159,14 @@ pub(crate) async fn run(
|
||||||
} else {
|
} else {
|
||||||
debug!("Syncing ephemeral environment.");
|
debug!("Syncing ephemeral environment.");
|
||||||
|
|
||||||
let client_builder = BaseClientBuilder::new()
|
|
||||||
.connectivity(connectivity)
|
|
||||||
.native_tls(native_tls);
|
|
||||||
|
|
||||||
// Discover an interpreter.
|
// Discover an interpreter.
|
||||||
let interpreter = if let Some(project_env) = &project_env {
|
let interpreter = if let Some(base_env) = &base_env {
|
||||||
project_env.interpreter().clone()
|
base_env.interpreter().clone()
|
||||||
} else {
|
} else {
|
||||||
|
let client_builder = BaseClientBuilder::new()
|
||||||
|
.connectivity(connectivity)
|
||||||
|
.native_tls(native_tls);
|
||||||
|
|
||||||
// Note we force preview on during `uv run` for now since the entire interface is in preview
|
// Note we force preview on during `uv run` for now since the entire interface is in preview
|
||||||
Toolchain::find_or_fetch(
|
Toolchain::find_or_fetch(
|
||||||
python.as_deref().map(ToolchainRequest::parse),
|
python.as_deref().map(ToolchainRequest::parse),
|
||||||
|
@ -194,7 +238,7 @@ pub(crate) async fn run(
|
||||||
.map(PythonEnvironment::scripts)
|
.map(PythonEnvironment::scripts)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(
|
.chain(
|
||||||
project_env
|
base_env
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(PythonEnvironment::scripts)
|
.map(PythonEnvironment::scripts)
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
|
@ -217,7 +261,7 @@ pub(crate) async fn run(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.chain(
|
.chain(
|
||||||
project_env
|
base_env
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(PythonEnvironment::site_packages)
|
.map(PythonEnvironment::site_packages)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue