diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 587f0d8330493..41790bd11c9d7 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -235,6 +235,14 @@ pub struct GlobalArgs { /// Change to the given directory prior to running the command. #[arg(global = true, long, hide = true)] pub directory: Option, + + /// Run the command within the given project. + /// + /// All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking + /// up the directory tree from the project root, while relative paths will be resolved relative + /// to the current working directory. + #[arg(global = true, long)] + pub project: Option, } #[derive(Debug, Copy, Clone, clap::ValueEnum)] diff --git a/crates/uv-settings/src/lib.rs b/crates/uv-settings/src/lib.rs index 0edb30e6fd121..9300b2b65f3cf 100644 --- a/crates/uv-settings/src/lib.rs +++ b/crates/uv-settings/src/lib.rs @@ -63,8 +63,8 @@ impl FilesystemOptions { /// /// The search starts at the given path and goes up the directory tree until a `uv.toml` file or /// `pyproject.toml` file is found. - pub fn find(path: impl AsRef) -> Result, Error> { - for ancestor in path.as_ref().ancestors() { + pub fn find(path: &Path) -> Result, Error> { + for ancestor in path.ancestors() { match Self::from_directory(ancestor) { Ok(Some(options)) => { return Ok(Some(options)); @@ -91,9 +91,9 @@ impl FilesystemOptions { /// Load a [`FilesystemOptions`] from a directory, preferring a `uv.toml` file over a /// `pyproject.toml` file. - pub fn from_directory(dir: impl AsRef) -> Result, Error> { + pub fn from_directory(dir: &Path) -> Result, Error> { // Read a `uv.toml` file in the current directory. - let path = dir.as_ref().join("uv.toml"); + let path = dir.join("uv.toml"); match fs_err::read_to_string(&path) { Ok(content) => { let options: Options = toml::from_str(&content) @@ -101,7 +101,7 @@ impl FilesystemOptions { // If the directory also contains a `[tool.uv]` table in a `pyproject.toml` file, // warn. - let pyproject = dir.as_ref().join("pyproject.toml"); + let pyproject = dir.join("pyproject.toml"); if let Some(pyproject) = fs_err::read_to_string(pyproject) .ok() .and_then(|content| toml::from_str::(&content).ok()) @@ -121,7 +121,7 @@ impl FilesystemOptions { } // Read a `pyproject.toml` file in the current directory. - let path = dir.as_ref().join("pyproject.toml"); + let path = dir.join("pyproject.toml"); match fs_err::read_to_string(&path) { Ok(content) => { // Parse, but skip any `pyproject.toml` that doesn't have a `[tool.uv]` section. @@ -130,14 +130,14 @@ impl FilesystemOptions { let Some(tool) = pyproject.tool else { debug!( "Skipping `pyproject.toml` in `{}` (no `[tool]` section)", - dir.as_ref().display() + dir.display() ); return Ok(None); }; let Some(options) = tool.uv else { debug!( "Skipping `pyproject.toml` in `{}` (no `[tool.uv]` section)", - dir.as_ref().display() + dir.display() ); return Ok(None); }; diff --git a/crates/uv/src/commands/build.rs b/crates/uv/src/commands/build.rs index 6a2264d2b4599..b8815d9a36e87 100644 --- a/crates/uv/src/commands/build.rs +++ b/crates/uv/src/commands/build.rs @@ -15,7 +15,7 @@ use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{BuildKind, BuildOutput, Concurrency, Constraints, HashCheckingMode}; use uv_dispatch::BuildDispatch; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_normalize::PackageName; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonEnvironment, PythonInstallation, @@ -29,6 +29,7 @@ use uv_workspace::{DiscoveryOptions, Workspace}; /// Build source distributions and wheels. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn build( + project_dir: &Path, src: Option, package: Option, output_dir: Option, @@ -48,6 +49,7 @@ pub(crate) async fn build( printer: Printer, ) -> Result { let assets = build_impl( + project_dir, src.as_deref(), package.as_ref(), output_dir.as_deref(), @@ -89,6 +91,7 @@ pub(crate) async fn build( #[allow(clippy::fn_params_excessive_bools)] async fn build_impl( + project_dir: &Path, src: Option<&Path>, package: Option<&PackageName>, output_dir: Option<&Path>, @@ -149,7 +152,7 @@ async fn build_impl( Source::Directory(Cow::Owned(src)) } } else { - Source::Directory(Cow::Borrowed(&*CWD)) + Source::Directory(Cow::Borrowed(project_dir)) }; // Attempt to discover the workspace; on failure, save the error for later. diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 9b43b71a8a880..2fdfdacc9ae88 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -21,7 +21,7 @@ use uv_configuration::{ }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_git::{GitReference, GIT_STORE}; use uv_normalize::PackageName; use uv_python::{ @@ -51,6 +51,7 @@ use crate::settings::{ResolverInstallerSettings, ResolverInstallerSettingsRef}; /// Add one or more packages to the project requirements. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn add( + project_dir: &Path, locked: bool, frozen: bool, no_sync: bool, @@ -131,7 +132,7 @@ pub(crate) async fn add( let python_request = if let Some(request) = python.as_deref() { // (1) Explicit request from user PythonRequest::parse(request) - } else if let Some(request) = PythonVersionFile::discover(&*CWD, false, false) + } else if let Some(request) = PythonVersionFile::discover(project_dir, false, false) .await? .and_then(PythonVersionFile::into_version) { @@ -162,7 +163,7 @@ pub(crate) async fn add( let python_request = if let Some(request) = python.as_deref() { // (1) Explicit request from user Some(PythonRequest::parse(request)) - } else if let Some(request) = PythonVersionFile::discover(&*CWD, false, false) + } else if let Some(request) = PythonVersionFile::discover(project_dir, false, false) .await? .and_then(PythonVersionFile::into_version) { @@ -196,13 +197,13 @@ pub(crate) async fn add( // Find the project in the workspace. let project = if let Some(package) = package { VirtualProject::Project( - Workspace::discover(&CWD, &DiscoveryOptions::default()) + Workspace::discover(project_dir, &DiscoveryOptions::default()) .await? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) } else { - VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await? + VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? }; // For non-project workspace roots, allow dev dependencies, but nothing else. diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index 4fd5954899037..562c3a28e4353 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -3,7 +3,7 @@ use std::env; use anyhow::{Context, Result}; use itertools::Itertools; use owo_colors::OwoColorize; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use uv_cache::Cache; use uv_client::Connectivity; @@ -11,7 +11,6 @@ use uv_configuration::{ Concurrency, DevMode, DevSpecification, EditableMode, ExportFormat, ExtrasSpecification, InstallOptions, }; -use uv_fs::CWD; use uv_normalize::{PackageName, DEV_DEPENDENCIES}; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_resolver::RequirementsTxtExport; @@ -27,6 +26,7 @@ use crate::settings::ResolverSettings; /// Export the project's `uv.lock` in an alternate format. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn export( + project_dir: &Path, format: ExportFormat, package: Option, hashes: bool, @@ -51,14 +51,14 @@ pub(crate) async fn export( // Identify the project. let project = if let Some(package) = package { VirtualProject::Project( - Workspace::discover(&CWD, &DiscoveryOptions::default()) + Workspace::discover(project_dir, &DiscoveryOptions::default()) .await? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) } else if frozen { VirtualProject::discover( - &CWD, + project_dir, &DiscoveryOptions { members: MemberDiscovery::None, ..DiscoveryOptions::default() @@ -66,7 +66,7 @@ pub(crate) async fn export( ) .await? } else { - VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await? + VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? }; let VirtualProject::Project(project) = project else { diff --git a/crates/uv/src/commands/project/init.rs b/crates/uv/src/commands/project/init.rs index bda3af8fcef97..8370bec18eab6 100644 --- a/crates/uv/src/commands/project/init.rs +++ b/crates/uv/src/commands/project/init.rs @@ -8,7 +8,7 @@ use pep508_rs::PackageName; use tracing::{debug, warn}; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity}; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile, VersionRequest, @@ -25,6 +25,7 @@ use crate::printer::Printer; /// Add one or more packages to the project requirements. #[allow(clippy::single_match_else, clippy::fn_params_excessive_bools)] pub(crate) async fn init( + project_dir: &Path, explicit_path: Option, name: Option, package: bool, @@ -42,7 +43,7 @@ pub(crate) async fn init( ) -> Result { // Default to the current directory if a path was not provided. let path = match explicit_path { - None => CWD.to_path_buf(), + None => project_dir.to_path_buf(), Some(ref path) => std::path::absolute(path)?, }; diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index 054e1619fb907..6506603dc9d03 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -1,11 +1,11 @@ #![allow(clippy::single_match_else)] -use std::collections::BTreeSet; -use std::fmt::Write; - use anstream::eprint; use owo_colors::OwoColorize; use rustc_hash::{FxBuildHasher, FxHashMap}; +use std::collections::BTreeSet; +use std::fmt::Write; +use std::path::Path; use tracing::debug; use distribution_types::{ @@ -22,7 +22,6 @@ use uv_configuration::{ }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; -use uv_fs::CWD; use uv_git::ResolvedRepositoryReference; use uv_normalize::{PackageName, DEV_DEPENDENCIES}; use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; @@ -68,6 +67,7 @@ impl LockResult { /// Resolve the project requirements into a lockfile. pub(crate) async fn lock( + project_dir: &Path, locked: bool, frozen: bool, python: Option, @@ -81,7 +81,7 @@ pub(crate) async fn lock( printer: Printer, ) -> anyhow::Result { // Find the project requirements. - let workspace = Workspace::discover(&CWD, &DiscoveryOptions::default()).await?; + let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; // Find an interpreter for the project let interpreter = FoundInterpreter::discover( diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index f018d8c54d8c4..6b9c203c72d40 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -1,13 +1,13 @@ -use std::fmt::Write; - use anyhow::{Context, Result}; +use std::fmt::Write; +use std::path::Path; use owo_colors::OwoColorize; use pep508_rs::PackageName; use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::{Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions}; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_scripts::Pep723Script; use uv_warnings::{warn_user, warn_user_once}; @@ -24,6 +24,7 @@ use crate::settings::ResolverInstallerSettings; /// Remove one or more packages from the project requirements. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn remove( + project_dir: &Path, locked: bool, frozen: bool, no_sync: bool, @@ -68,13 +69,13 @@ pub(crate) async fn remove( // Find the project in the workspace. let project = if let Some(package) = package { VirtualProject::Project( - Workspace::discover(&CWD, &DiscoveryOptions::default()) + Workspace::discover(project_dir, &DiscoveryOptions::default()) .await? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) } else { - VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await? + VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? }; Target::Project(project) diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index cbecf373abda3..b1cde871899de 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -18,7 +18,7 @@ use uv_configuration::{ Concurrency, DevMode, EditableMode, ExtrasSpecification, InstallOptions, SourceStrategy, }; use uv_distribution::LoweredRequirement; -use uv_fs::{PythonExt, Simplified, CWD}; +use uv_fs::{PythonExt, Simplified}; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::PackageName; use uv_python::{ @@ -47,6 +47,7 @@ use crate::settings::ResolverInstallerSettings; /// Run a command. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn run( + project_dir: &Path, script: Option, command: RunCommand, requirements: Vec, @@ -113,7 +114,7 @@ pub(crate) async fn run( let source = PythonRequestSource::UserRequest; let request = Some(PythonRequest::parse(request)); (source, request) - } else if let Some(file) = PythonVersionFile::discover(&*CWD, false, false).await? { + } else if let Some(file) = PythonVersionFile::discover(&project_dir, false, false).await? { // (2) Request from `.python-version` let source = PythonRequestSource::DotPythonVersion(file.file_name().to_string()); let request = file.into_version(); @@ -309,13 +310,13 @@ pub(crate) async fn run( // 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. Some(VirtualProject::Project( - Workspace::discover(&CWD, &DiscoveryOptions::default()) + Workspace::discover(project_dir, &DiscoveryOptions::default()) .await? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, )) } else { - match VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await { + match VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await { Ok(project) => { if no_project { debug!("Ignoring discovered project due to `--no-project`"); @@ -537,7 +538,7 @@ pub(crate) async fn run( Some(PythonRequest::parse(request)) // (2) Request from `.python-version` } else { - PythonVersionFile::discover(&*CWD, no_config, false) + PythonVersionFile::discover(&project_dir, no_config, false) .await? .and_then(PythonVersionFile::into_version) }; diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index f407235718376..d6a38516f0a40 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -13,6 +13,7 @@ use pypi_types::{ LenientRequirement, ParsedArchiveUrl, ParsedGitUrl, ParsedUrl, VerbatimParsedUrl, }; use std::borrow::Cow; +use std::path::Path; use std::str::FromStr; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; @@ -21,7 +22,6 @@ use uv_configuration::{ HashCheckingMode, InstallOptions, }; use uv_dispatch::BuildDispatch; -use uv_fs::CWD; use uv_installer::SitePackages; use uv_normalize::{PackageName, DEV_DEPENDENCIES}; use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest}; @@ -34,6 +34,7 @@ use uv_workspace::{DiscoveryOptions, InstallTarget, MemberDiscovery, VirtualProj /// Sync the project environment. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn sync( + project_dir: &Path, locked: bool, frozen: bool, package: Option, @@ -55,7 +56,7 @@ pub(crate) async fn sync( // Identify the project. let project = if frozen { VirtualProject::discover( - &CWD, + project_dir, &DiscoveryOptions { members: MemberDiscovery::None, ..DiscoveryOptions::default() @@ -64,13 +65,13 @@ pub(crate) async fn sync( .await? } else if let Some(package) = package.as_ref() { VirtualProject::Project( - Workspace::discover(&CWD, &DiscoveryOptions::default()) + Workspace::discover(project_dir, &DiscoveryOptions::default()) .await? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) } else { - VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await? + VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await? }; // Identify the target. diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index d9e512079e1c1..b2a2d1e94d40d 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -1,12 +1,11 @@ -use std::fmt::Write; - use anyhow::Result; +use std::fmt::Write; +use std::path::Path; use pep508_rs::PackageName; use uv_cache::Cache; use uv_client::Connectivity; use uv_configuration::{Concurrency, TargetTriple}; -use uv_fs::CWD; use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion}; use uv_resolver::TreeDisplay; use uv_workspace::{DiscoveryOptions, Workspace}; @@ -21,6 +20,7 @@ use crate::settings::ResolverSettings; /// Run a command. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn tree( + project_dir: &Path, locked: bool, frozen: bool, universal: bool, @@ -42,7 +42,7 @@ pub(crate) async fn tree( printer: Printer, ) -> Result { // Find the project requirements. - let workspace = Workspace::discover(&CWD, &DiscoveryOptions::default()).await?; + let workspace = Workspace::discover(project_dir, &DiscoveryOptions::default()).await?; // Find an interpreter for the project let interpreter = FoundInterpreter::discover( diff --git a/crates/uv/src/commands/python/find.rs b/crates/uv/src/commands/python/find.rs index 3f9658b64d6c1..cb99aa154b936 100644 --- a/crates/uv/src/commands/python/find.rs +++ b/crates/uv/src/commands/python/find.rs @@ -1,8 +1,9 @@ use anstream::println; use anyhow::Result; +use std::path::Path; use uv_cache::Cache; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile, VersionRequest, @@ -15,6 +16,7 @@ use crate::commands::{project::find_requires_python, ExitStatus}; /// Find a Python interpreter. pub(crate) async fn find( + project_dir: &Path, request: Option, no_project: bool, no_config: bool, @@ -33,23 +35,24 @@ pub(crate) async fn find( // (2) Request from `.python-version` if request.is_none() { - request = PythonVersionFile::discover(&*CWD, no_config, false) + request = PythonVersionFile::discover(project_dir, no_config, false) .await? .and_then(PythonVersionFile::into_version); } // (3) `Requires-Python` in `pyproject.toml` if request.is_none() && !no_project { - let project = match VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await { - Ok(project) => Some(project), - Err(WorkspaceError::MissingProject(_)) => None, - Err(WorkspaceError::MissingPyprojectToml) => None, - Err(WorkspaceError::NonWorkspace(_)) => None, - Err(err) => { - warn_user_once!("{err}"); - None - } - }; + let project = + match VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await { + Ok(project) => Some(project), + Err(WorkspaceError::MissingProject(_)) => None, + Err(WorkspaceError::MissingPyprojectToml) => None, + Err(WorkspaceError::NonWorkspace(_)) => None, + Err(err) => { + warn_user_once!("{err}"); + None + } + }; if let Some(project) = project { request = find_requires_python(project.workspace())? diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 55b82476efa8e..ac373eafd7c15 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -1,15 +1,14 @@ -use std::collections::BTreeSet; -use std::fmt::Write; - use anyhow::Result; use fs_err as fs; use futures::stream::FuturesUnordered; use futures::StreamExt; use itertools::Itertools; use owo_colors::OwoColorize; +use std::collections::BTreeSet; +use std::fmt::Write; +use std::path::Path; use uv_client::Connectivity; -use uv_fs::CWD; use uv_python::downloads::{DownloadResult, ManagedPythonDownload, PythonDownloadRequest}; use uv_python::managed::{ManagedPythonInstallation, ManagedPythonInstallations}; use uv_python::{PythonDownloads, PythonRequest, PythonVersionFile}; @@ -21,6 +20,7 @@ use crate::printer::Printer; /// Download and install Python versions. pub(crate) async fn install( + project_dir: &Path, targets: Vec, reinstall: bool, python_downloads: PythonDownloads, @@ -38,9 +38,9 @@ pub(crate) async fn install( let targets = targets.into_iter().collect::>(); let requests: Vec<_> = if targets.is_empty() { - PythonVersionFile::discover(&*CWD, no_config, true) + PythonVersionFile::discover(project_dir, no_config, true) .await? - .map(uv_python::PythonVersionFile::into_versions) + .map(PythonVersionFile::into_versions) .unwrap_or_else(|| vec![PythonRequest::Default]) } else { targets diff --git a/crates/uv/src/commands/python/pin.rs b/crates/uv/src/commands/python/pin.rs index 0f5b4681e85f0..e55c2da4d5f3c 100644 --- a/crates/uv/src/commands/python/pin.rs +++ b/crates/uv/src/commands/python/pin.rs @@ -1,4 +1,5 @@ use std::fmt::Write; +use std::path::Path; use std::str::FromStr; use anyhow::{bail, Result}; @@ -6,7 +7,7 @@ use owo_colors::OwoColorize; use tracing::debug; use uv_cache::Cache; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile, PYTHON_VERSION_FILENAME, @@ -19,6 +20,7 @@ use crate::printer::Printer; /// Pin to a specific Python version. pub(crate) async fn pin( + project_dir: &Path, request: Option, resolved: bool, python_preference: PythonPreference, @@ -29,7 +31,7 @@ pub(crate) async fn pin( let virtual_project = if no_project { None } else { - match VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await { + match VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await { Ok(virtual_project) => Some(virtual_project), Err(err) => { debug!("Failed to discover virtual project: {err}"); @@ -38,7 +40,7 @@ pub(crate) async fn pin( } }; - let version_file = PythonVersionFile::discover(&*CWD, false, false).await; + let version_file = PythonVersionFile::discover(project_dir, false, false).await; let Some(request) = request else { // Display the current pinned Python version @@ -124,8 +126,8 @@ pub(crate) async fn pin( let existing = version_file.ok().flatten(); // TODO(zanieb): Allow updating the discovered version file with an `--update` flag. - let new = - PythonVersionFile::new(CWD.join(PYTHON_VERSION_FILENAME)).with_versions(vec![request]); + let new = PythonVersionFile::new(project_dir.join(PYTHON_VERSION_FILENAME)) + .with_versions(vec![request]); new.write().await?; diff --git a/crates/uv/src/commands/venv.rs b/crates/uv/src/commands/venv.rs index 39d957a5c1d35..8e089f7080cfc 100644 --- a/crates/uv/src/commands/venv.rs +++ b/crates/uv/src/commands/venv.rs @@ -20,7 +20,7 @@ use uv_configuration::{ NoBinary, NoBuild, SourceStrategy, TrustedHost, }; use uv_dispatch::BuildDispatch; -use uv_fs::{Simplified, CWD}; +use uv_fs::Simplified; use uv_python::{ EnvironmentPreference, PythonDownloads, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile, VersionRequest, @@ -41,6 +41,7 @@ use crate::printer::Printer; /// Create a virtual environment. #[allow(clippy::unnecessary_wraps, clippy::fn_params_excessive_bools)] pub(crate) async fn venv( + project_dir: &Path, path: Option, python_request: Option<&str>, python_preference: PythonPreference, @@ -66,6 +67,7 @@ pub(crate) async fn venv( relocatable: bool, ) -> Result { match venv_impl( + project_dir, path, python_request, link_mode, @@ -122,6 +124,7 @@ enum VenvError { /// Create a virtual environment. #[allow(clippy::fn_params_excessive_bools)] async fn venv_impl( + project_dir: &Path, path: Option, python_request: Option<&str>, link_mode: LinkMode, @@ -149,7 +152,7 @@ async fn venv_impl( let project = if no_project { None } else { - match VirtualProject::discover(&CWD, &DiscoveryOptions::default()).await { + match VirtualProject::discover(project_dir, &DiscoveryOptions::default()).await { Ok(project) => Some(project), Err(WorkspaceError::MissingProject(_)) => None, Err(WorkspaceError::MissingPyprojectToml) => None, @@ -169,7 +172,8 @@ async fn venv_impl( // Only use the project environment path if we're invoked from the root // This isn't strictly necessary and we may want to change it later, but this // avoids a breaking change when adding project environment support to `uv venv`. - (project.workspace().install_path() == &*CWD).then(|| project.workspace().venv()) + (project.workspace().install_path() == project_dir) + .then(|| project.workspace().venv()) }) .unwrap_or(PathBuf::from(".venv")), ); @@ -185,7 +189,7 @@ async fn venv_impl( // (2) Request from `.python-version` if interpreter_request.is_none() { - interpreter_request = PythonVersionFile::discover(&*CWD, no_config, false) + interpreter_request = PythonVersionFile::discover(project_dir, no_config, false) .await .into_diagnostic()? .and_then(PythonVersionFile::into_version); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 56e74e3bef037..02bae392c77d2 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1,6 +1,8 @@ +use std::borrow::Cow; use std::ffi::OsString; use std::fmt::Write; use std::io::stdout; +use std::path::Path; use std::process::ExitCode; use anstream::eprintln; @@ -67,6 +69,17 @@ async fn run(cli: Cli) -> Result { std::env::set_current_dir(directory)?; } + // Determine the project directory. + let project_dir = cli + .top_level + .global_args + .project + .as_deref() + .map(std::path::absolute) + .transpose()? + .map(Cow::Owned) + .unwrap_or_else(|| Cow::Borrowed(&*CWD)); + // The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs. let deprecated_isolated = if cli.top_level.global_args.isolated { match &*cli.command { @@ -111,7 +124,7 @@ async fn run(cli: Cli) -> Result { .file_name() .is_some_and(|file_name| file_name == "pyproject.toml") { - warn_user!("The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--directory` argument instead."); + warn_user!("The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--project` argument instead."); } Some(FilesystemOptions::from_file(config_file)?) } else if deprecated_isolated || cli.top_level.no_config { @@ -119,12 +132,14 @@ async fn run(cli: Cli) -> Result { } else if matches!(&*cli.command, Commands::Tool(_)) { // For commands that operate at the user-level, ignore local configuration. FilesystemOptions::user()? - } else if let Ok(workspace) = Workspace::discover(&CWD, &DiscoveryOptions::default()).await { + } else if let Ok(workspace) = + Workspace::discover(&project_dir, &DiscoveryOptions::default()).await + { let project = FilesystemOptions::find(workspace.install_path())?; let user = FilesystemOptions::user()?; project.combine(user) } else { - let project = FilesystemOptions::find(&*CWD)?; + let project = FilesystemOptions::find(&project_dir)?; let user = FilesystemOptions::user()?; project.combine(user) }; @@ -680,6 +695,7 @@ async fn run(cli: Cli) -> Result { .collect::>(); commands::build( + &project_dir, args.src, args.package, args.out_dir, @@ -728,6 +744,7 @@ async fn run(cli: Cli) -> Result { }); commands::venv( + &project_dir, args.path, args.settings.python.as_deref(), globals.python_preference, @@ -757,6 +774,7 @@ async fn run(cli: Cli) -> Result { Commands::Project(project) => { Box::pin(run_project( project, + &project_dir, run_command, script, globals, @@ -1001,6 +1019,7 @@ async fn run(cli: Cli) -> Result { show_settings!(args); commands::python_install( + &project_dir, args.targets, args.reinstall, globals.python_downloads, @@ -1030,6 +1049,7 @@ async fn run(cli: Cli) -> Result { let cache = cache.init()?; commands::python_find( + &project_dir, args.request, args.no_project, cli.top_level.no_config, @@ -1049,6 +1069,7 @@ async fn run(cli: Cli) -> Result { let cache = cache.init()?; commands::python_pin( + &project_dir, args.request, args.resolved, globals.python_preference, @@ -1070,6 +1091,7 @@ async fn run(cli: Cli) -> Result { /// Run a [`ProjectCommand`]. async fn run_project( project_command: Box, + project_dir: &Path, command: Option, script: Option, globals: GlobalSettings, @@ -1104,6 +1126,7 @@ async fn run_project( let cache = cache.init()?; commands::init( + project_dir, args.path, args.name, args.package, @@ -1153,6 +1176,7 @@ async fn run_project( let command = command.expect("run command is required"); Box::pin(commands::run( + project_dir, script, command, requirements, @@ -1192,6 +1216,7 @@ async fn run_project( ); commands::sync( + project_dir, args.locked, args.frozen, args.package, @@ -1224,6 +1249,7 @@ async fn run_project( ); commands::lock( + project_dir, args.locked, args.frozen, args.python, @@ -1262,6 +1288,7 @@ async fn run_project( .collect::>(); Box::pin(commands::add( + project_dir, args.locked, args.frozen, args.no_sync, @@ -1300,6 +1327,7 @@ async fn run_project( ); commands::remove( + project_dir, args.locked, args.frozen, args.no_sync, @@ -1328,6 +1356,7 @@ async fn run_project( let cache = cache.init()?; commands::tree( + project_dir, args.locked, args.frozen, args.universal, @@ -1359,6 +1388,7 @@ async fn run_project( let cache = cache.init()?; commands::export( + project_dir, args.format, args.package, args.hashes, diff --git a/crates/uv/tests/help.rs b/crates/uv/tests/help.rs index 516408dcdad5f..f575d814c1bed 100644 --- a/crates/uv/tests/help.rs +++ b/crates/uv/tests/help.rs @@ -55,6 +55,7 @@ fn help() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, @@ -119,6 +120,7 @@ fn help_flag() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, @@ -182,6 +184,7 @@ fn help_short_flag() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, @@ -333,6 +336,13 @@ fn help_subcommand() { For example, spinners or progress bars. + --project + Run the command within the given project. + + All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking + up the directory tree from the project root, while relative paths will be resolved + relative to the current working directory. + --config-file The path to a `uv.toml` file to use for configuration. @@ -482,6 +492,13 @@ fn help_subsubcommand() { For example, spinners or progress bars. + --project + Run the command within the given project. + + All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking + up the directory tree from the project root, while relative paths will be resolved + relative to the current working directory. + --config-file The path to a `uv.toml` file to use for configuration. @@ -550,6 +567,7 @@ fn help_flag_subcommand() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, @@ -602,6 +620,7 @@ fn help_flag_subsubcommand() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, @@ -740,6 +759,7 @@ fn help_with_global_option() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, @@ -841,6 +861,7 @@ fn help_with_no_pager() { certificate store [env: UV_NATIVE_TLS=] --offline Disable network access --no-progress Hide all progress outputs + --project Run the command within the given project --config-file The path to a `uv.toml` file to use for configuration [env: UV_CONFIG_FILE=] --no-config Avoid discovering configuration files (`pyproject.toml`, diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index 7c61239ea9d0e..c63df0d0c9626 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -1322,7 +1322,6 @@ fn run_from_directory() -> Result<()> { let context = TestContext::new_with_versions(&["3.11", "3.12"]); let project_dir = context.temp_dir.child("project"); - project_dir.create_dir_all()?; project_dir .child(PYTHON_VERSION_FILENAME) .write_str("3.12")?; @@ -1332,7 +1331,7 @@ fn run_from_directory() -> Result<()> { [project] name = "foo" version = "1.0.0" - requires-python = ">=3.11, <4" + requires-python = ">=3.11" dependencies = [] [project.scripts] @@ -1343,6 +1342,7 @@ fn run_from_directory() -> Result<()> { build-backend = "setuptools.build_meta" "# })?; + let main_script = project_dir.child("main.py"); main_script.write_str(indoc! { r" import platform @@ -1352,25 +1352,103 @@ fn run_from_directory() -> Result<()> { " })?; - let mut command = context.run(); - let command_with_args = command.arg("--directory").arg("project").arg("main"); - - uv_snapshot!(context.filters(), command_with_args, @r###" + // Use `--project`, which resolves configuration relative to the provided directory, but paths + // relative to the current working directory. + uv_snapshot!(context.filters(), context.run().arg("--project").arg("project").arg("main"), @r###" success: true exit_code: 0 ----- stdout ----- 3.12.[X] ----- stderr ----- - warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored + warning: `VIRTUAL_ENV=.venv` does not match the project environment path `project/.venv` and will be ignored Using Python 3.12.[X] interpreter at: [PYTHON-3.12] - Creating virtual environment at: .venv + Creating virtual environment at: project/.venv Resolved 1 package in [TIME] Prepared 1 package in [TIME] Installed 1 package in [TIME] + foo==1.0.0 (from file://[TEMP_DIR]/project) "###); + uv_snapshot!(context.filters(), context.run().arg("--project").arg("project").arg("./project/main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `VIRTUAL_ENV=.venv` does not match the project environment path `project/.venv` and will be ignored + Resolved 1 package in [TIME] + Audited 1 package in [TIME] + "###); + + // Use `--directory`, which switches to the provided directory entirely. + uv_snapshot!(context.filters(), context.run().arg("--directory").arg("project").arg("main"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 3.12.[X] + + ----- stderr ----- + warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored + Resolved 1 package in [TIME] + Audited 1 package in [TIME] + "###); + + uv_snapshot!(context.filters(), context.run().arg("--directory").arg("project").arg("./main.py"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored + Resolved 1 package in [TIME] + Audited 1 package in [TIME] + "###); + + uv_snapshot!(context.filters(), context.run().arg("--directory").arg("project").arg("./project/main.py"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored + Resolved 1 package in [TIME] + Audited 1 package in [TIME] + error: Failed to spawn: `./project/main.py` + Caused by: No such file or directory (os error 2) + "###); + + // Even if we write a `.python-version` file in the current directory, we should prefer the + // one in the project directory in both cases. + context + .temp_dir + .child(PYTHON_VERSION_FILENAME) + .write_str("3.11")?; + + uv_snapshot!(context.filters(), context.run().arg("--project").arg("project").arg("main"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 3.12.[X] + + ----- stderr ----- + warning: `VIRTUAL_ENV=.venv` does not match the project environment path `project/.venv` and will be ignored + Resolved 1 package in [TIME] + Audited 1 package in [TIME] + "###); + + uv_snapshot!(context.filters(), context.run().arg("--directory").arg("project").arg("main"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 3.12.[X] + + ----- stderr ----- + warning: `VIRTUAL_ENV=[VENV]/` does not match the project environment path `.venv` and will be ignored + Resolved 1 package in [TIME] + Audited 1 package in [TIME] + "###); + Ok(()) } diff --git a/crates/uv/tests/show_settings.rs b/crates/uv/tests/show_settings.rs index 4709dacf566db..c0703ad88f7af 100644 --- a/crates/uv/tests/show_settings.rs +++ b/crates/uv/tests/show_settings.rs @@ -3180,7 +3180,7 @@ fn resolve_config_file() -> anyhow::Result<()> { ----- stdout ----- ----- stderr ----- - warning: The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--directory` argument instead. + warning: The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--project` argument instead. error: Failed to parse: `[CACHE_DIR]/pyproject.toml` Caused by: TOML parse error at line 9, column 3 | diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a7ee29fd49826..ec7ce418b0bf8 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -301,6 +301,10 @@ uv run [OPTIONS]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use for the run environment.

    If the interpreter request is satisfied by a discovered environment, the environment will be used.

    @@ -493,6 +497,10 @@ uv init [OPTIONS] [PATH]

    When using --app, this will include a [project.scripts] entrypoint and use a src/ project structure.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use to determine the minimum supported Python version.

    See uv python to view supported request formats.

    @@ -763,6 +771,10 @@ uv add [OPTIONS] >
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use for resolving and syncing.

    See uv python for details on Python discovery and supported request formats.

    @@ -1059,6 +1071,10 @@ uv remove [OPTIONS] ...
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use for resolving and syncing.

    See uv python for details on Python discovery and supported request formats.

    @@ -1371,6 +1387,10 @@ uv sync [OPTIONS]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use for the project environment.

    By default, the first interpreter that meets the project’s requires-python constraint is used.

    @@ -1628,6 +1648,10 @@ uv lock [OPTIONS]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use during resolution.

    A Python interpreter is required for building source distributions to determine package metadata when there are not wheels.

    @@ -1931,6 +1955,10 @@ uv export [OPTIONS]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use during resolution.

    A Python interpreter is required for building source distributions to determine package metadata when there are not wheels.

    @@ -2191,6 +2219,10 @@ uv tree [OPTIONS]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --prune prune

    Prune the given package from the display of the dependency tree

    --python, -p python

    The Python interpreter to use for locking and filtering.

    @@ -2536,6 +2568,10 @@ uv tool run [OPTIONS] [COMMAND]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use to build the run environment.

    See uv python for details on Python discovery and supported request formats.

    @@ -2806,6 +2842,10 @@ uv tool install [OPTIONS]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use to build the tool environment.

    See uv python for details on Python discovery and supported request formats.

    @@ -3070,6 +3110,10 @@ uv tool upgrade [OPTIONS] ...
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3177,6 +3221,10 @@ uv tool list [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --quiet, -q

    Do not print any output

    --show-paths

    Whether to display the path to each tool environment and installed executable

    @@ -3260,6 +3308,10 @@ uv tool uninstall [OPTIONS] ...

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3353,6 +3405,10 @@ uv tool update-shell [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3464,6 +3520,10 @@ uv tool dir [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3635,6 +3695,10 @@ uv python list [OPTIONS]

    By default, available downloads for the current platform are shown.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3742,6 +3806,10 @@ uv python install [OPTIONS] [TARGETS]...

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3849,6 +3917,10 @@ uv python find [OPTIONS] [REQUEST]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -3961,6 +4033,10 @@ uv python pin [OPTIONS] [REQUEST]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -4058,6 +4134,10 @@ uv python dir [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -4155,6 +4235,10 @@ uv python uninstall [OPTIONS] ...

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -4485,6 +4569,10 @@ uv pip compile [OPTIONS] ...
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python python

    The Python interpreter to use during resolution.

    A Python interpreter is required for building source distributions to determine package metadata when there are not wheels.

    @@ -4808,6 +4896,10 @@ uv pip sync [OPTIONS] ...

    In general, prefer the use of --python to install into an alternate environment, as scripts and other artifacts installed via --prefix will reference the installing interpreter, rather than any interpreter added to the --prefix directory, rendering them non-portable.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter into which packages should be installed.

    By default, syncing requires a virtual environment. A path to an alternative Python can be provided, but it is only recommended in continuous integration (CI) environments and should be used with caution, as it can modify the system Python installation.

    @@ -5170,6 +5262,10 @@ uv pip install [OPTIONS] |--editable if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter into which packages should be installed.

    By default, installation requires a virtual environment. A path to an alternative Python can be provided, but it is only recommended in continuous integration (CI) environments and should be used with caution, as it can modify the system Python installation.

    @@ -5406,6 +5502,10 @@ uv pip uninstall [OPTIONS] >
    --prefix prefix

    Uninstall packages from the specified --prefix directory

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter from which packages should be uninstalled.

    By default, uninstallation requires a virtual environment. A path to an alternative Python can be provided, but it is only recommended in continuous integration (CI) environments and should be used with caution, as it can modify the system Python installation.

    @@ -5513,6 +5613,10 @@ uv pip freeze [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter for which packages should be listed.

    By default, uv lists packages in a virtual environment but will show packages in a system Python environment if no virtual environment is found.

    @@ -5634,6 +5738,10 @@ uv pip list [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter for which packages should be listed.

    By default, uv lists packages in a virtual environment but will show packages in a system Python environment if no virtual environment is found.

    @@ -5743,6 +5851,10 @@ uv pip show [OPTIONS] [PACKAGE]...

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to find the package in.

    By default, uv looks for packages in a virtual environment but will look for packages in a system Python environment if no virtual environment is found.

    @@ -5855,6 +5967,10 @@ uv pip tree [OPTIONS]
    --package package

    Display only the specified packages

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --prune prune

    Prune the given package from the display of the dependency tree

    --python, -p python

    The Python interpreter for which packages should be listed.

    @@ -5962,6 +6078,10 @@ uv pip check [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter for which packages should be checked.

    By default, uv checks packages in a virtual environment but will check packages in a system Python environment if no virtual environment is found.

    @@ -6173,6 +6293,10 @@ uv venv [OPTIONS] [PATH]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --prompt prompt

    Provide an alternative prompt prefix for the virtual environment.

    By default, the prompt is dependent on whether a path was provided to uv venv. If provided (e.g, uv venv project), the prompt is set to the directory name. If not provided (uv venv), the prompt is set to the current directory’s name.

    @@ -6445,6 +6569,10 @@ uv build [OPTIONS] [SRC]
  • if-necessary-or-explicit: Allow pre-release versions if all versions of a package are pre-release, or if the package has an explicit pre-release marker in its version requirements
  • +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python, -p python

    The Python interpreter to use for the build environment.

    By default, builds are executed in isolated virtual environments. The discovered interpreter will be used to create those environments, and will be symlinked or copied in depending on the platform.

    @@ -6607,6 +6735,10 @@ uv cache clean [OPTIONS] [PACKAGE]...

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -6700,6 +6832,10 @@ uv cache prune [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -6795,6 +6931,10 @@ uv cache dir [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -6882,7 +7022,11 @@ uv version [OPTIONS]

    When disabled, uv will only use locally cached data and locally available files.

    -
    --output-format output-format
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    +
    --output-format output-format
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    + +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.

    @@ -6915,7 +7059,7 @@ Generate shell completion

    Usage

    ``` -uv generate-shell-completion +uv generate-shell-completion [OPTIONS] ```

    Arguments

    @@ -6924,7 +7068,13 @@ uv generate-shell-completion
    +

    Options

    + +
    --project project

    Run the command within the given project.

    +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    + +
    ## uv help @@ -6993,6 +7143,10 @@ uv help [OPTIONS] [COMMAND]...

    When disabled, uv will only use locally cached data and locally available files.

    +
    --project project

    Run the command within the given project.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, while relative paths will be resolved relative to the current working directory.

    +
    --python-preference python-preference

    Whether to prefer uv-managed or system Python installations.

    By default, uv prefers using Python versions it manages. However, it will use system Python installations if a uv-managed Python is not installed. This option allows prioritizing or ignoring system Python installations.