diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 587f0d833049..84614fab322e 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -235,6 +235,19 @@ 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 directory. + /// + /// All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking + /// up the directory tree from the project root, as will the project's virtual environment + /// (`.venv`). + /// + /// Other command-line arguments (such as relative paths) will be resolved relative + /// to the current working directory. + /// + /// This setting has no effect when used in the `uv pip` interface. + #[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 0edb30e6fd12..9300b2b65f3c 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 6a2264d2b459..b8815d9a36e8 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 9b43b71a8a88..2fdfdacc9ae8 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 4fd595489903..562c3a28e435 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 bda3af8fcef9..8370bec18eab 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 054e1619fb90..6506603dc9d0 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 f018d8c54d8c..6b9c203c72d4 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 cbecf373abda..b1cde871899d 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 f40723571837..d6a38516f0a4 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 d9e512079e1c..b2a2d1e94d40 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 3f9658b64d6c..cb99aa154b93 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 55b82476efa8..ac373eafd7c1 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 0f5b4681e85f..e55c2da4d5f3 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 39d957a5c1d3..8e089f7080cf 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 56e74e3bef03..02bae392c77d 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/common/mod.rs b/crates/uv/tests/common/mod.rs index 22339af658ea..866c161165ee 100644 --- a/crates/uv/tests/common/mod.rs +++ b/crates/uv/tests/common/mod.rs @@ -750,7 +750,7 @@ impl TestContext { } /// Generate various escaped regex patterns for the given path. - pub(crate) fn path_patterns(path: impl AsRef) -> Vec { + pub fn path_patterns(path: impl AsRef) -> Vec { let mut patterns = Vec::new(); // We can only canonicalize paths that exist already diff --git a/crates/uv/tests/help.rs b/crates/uv/tests/help.rs index 516408dcdad5..70f695a4f334 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 directory --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 directory --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 directory --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,18 @@ fn help_subcommand() { For example, spinners or progress bars. + --project + Run the command within the given project directory. + + All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking + up the directory tree from the project root, as will the project's virtual environment + (`.venv`). + + Other command-line arguments (such as relative paths) will be resolved relative to the + current working directory. + + This setting has no effect when used in the `uv pip` interface. + --config-file The path to a `uv.toml` file to use for configuration. @@ -482,6 +497,18 @@ fn help_subsubcommand() { For example, spinners or progress bars. + --project + Run the command within the given project directory. + + All `pyproject.toml`, `uv.toml`, and `.python-version` files will be discovered by walking + up the directory tree from the project root, as will the project's virtual environment + (`.venv`). + + Other command-line arguments (such as relative paths) will be resolved relative to the + current working directory. + + This setting has no effect when used in the `uv pip` interface. + --config-file The path to a `uv.toml` file to use for configuration. @@ -550,6 +577,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 directory --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 +630,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 directory --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 +769,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 directory --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 +871,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 directory --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 7c61239ea9d0..8e808babfd89 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -5,6 +5,7 @@ use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::{fixture::ChildPath, prelude::*}; use indoc::indoc; +use std::path::Path; use uv_python::PYTHON_VERSION_FILENAME; @@ -1319,10 +1320,9 @@ fn run_editable() -> Result<()> { #[test] fn run_from_directory() -> Result<()> { // default 3.11 so that the .python-version is meaningful - let context = TestContext::new_with_versions(&["3.11", "3.12"]); + let context = TestContext::new_with_versions(&["3.10", "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 +1332,7 @@ fn run_from_directory() -> Result<()> { [project] name = "foo" version = "1.0.0" - requires-python = ">=3.11, <4" + requires-python = ">=3.10" dependencies = [] [project.scripts] @@ -1343,6 +1343,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 +1353,130 @@ 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###" + let filters = TestContext::path_patterns(Path::new("project").join(".venv")) + .into_iter() + .map(|pattern| (pattern, "[PROJECT_VENV]/".to_string())) + .collect::>(); + let error = regex::escape("The system cannot find the path specified. (os error 3)"); + let filters = context + .filters() + .into_iter() + .chain( + filters + .iter() + .map(|(pattern, replacement)| (pattern.as_str(), replacement.as_str())), + ) + .chain(std::iter::once(( + error.as_str(), + "No such file or directory (os error 2)", + ))) + .collect::>(); + + // Use `--project`, which resolves configuration relative to the provided directory, but paths + // relative to the current working directory. + uv_snapshot!(filters.clone(), 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!(filters.clone(), 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!(filters.clone(), 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!(filters.clone(), 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!(filters.clone(), 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")?; + + project_dir + .child(PYTHON_VERSION_FILENAME) + .write_str("3.10")?; + + uv_snapshot!(filters.clone(), context.run().arg("--project").arg("project").arg("main"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 3.10.[X] + + ----- stderr ----- + warning: `VIRTUAL_ENV=.venv` does not match the project environment path `[PROJECT_VENV]/` and will be ignored + Using Python 3.10.[X] interpreter at: [PYTHON-3.10] + Removed virtual environment at: [PROJECT_VENV]/ + Creating virtual environment at: [PROJECT_VENV]/ + Resolved 1 package in [TIME] + Installed 1 package in [TIME] + + foo==1.0.0 (from file://[TEMP_DIR]/project) + "###); + + uv_snapshot!(filters.clone(), context.run().arg("--directory").arg("project").arg("main"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + 3.10.[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 4709dacf566d..c0703ad88f7a 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 a7ee29fd4982..8a6e8a2e75b8 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -301,6 +301,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +501,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +779,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +1083,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +1403,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +1668,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +1979,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +2247,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +2600,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +2878,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3150,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3265,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --quiet, -q

    Do not print any output

    --show-paths

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

    @@ -3260,6 +3356,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3457,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3576,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3755,14 @@ uv python list [OPTIONS]

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

    +
    --project project

    Run the command within the given project directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3870,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +3985,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +4105,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +4210,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +4315,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +4653,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +4984,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +5354,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +5598,14 @@ uv pip uninstall [OPTIONS] >
    --prefix prefix

    Uninstall packages from the specified --prefix directory

    +
    --project project

    Run the command within the given project directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +5713,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +5842,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +5959,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +6079,14 @@ uv pip tree [OPTIONS]
    --package package

    Display only the specified packages

    +
    --project project

    Run the command within the given project directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +6194,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +6413,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +6693,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +6863,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +6964,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +7067,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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 +7162,15 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    + +
    --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 +7203,7 @@ Generate shell completion

    Usage

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

    Arguments

    @@ -6924,7 +7212,17 @@ uv generate-shell-completion
    +

    Options

    +
    --project project

    Run the command within the given project directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    + +
    ## uv help @@ -6993,6 +7291,14 @@ 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 directory.

    + +

    All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project’s virtual environment (.venv).

    + +

    Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

    + +

    This setting has no effect when used in the uv pip interface.

    +
    --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.