From 96b889bce39b8e58142a2e045c285d60684a78b4 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 22 Jul 2025 08:32:45 -0500 Subject: [PATCH] Add hint to use `uv self version` when `uv version` cannot find a project (#14738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When users run `uv version` in a directory without a `pyproject.toml` file, they often intend to check uv's own version rather than a project's version. This change adds a helpful hint to guide users to the correct command. **Before:** ``` ❯ uv version error: No `pyproject.toml` found in current directory or any parent directory ``` **After:** ``` ❯ uv version error: No `pyproject.toml` found in current directory or any parent directory hint: If you meant to view uv's version, use `uv self version` instead ``` ## Changes - Modified `find_target()` function in `crates/uv/src/commands/project/version.rs` to catch `WorkspaceError::MissingPyprojectToml` specifically and enhance the error message with a helpful hint - Added import for `WorkspaceError` to access the specific error type - Updated existing tests to expect the new hint message in error output - Added new test case `version_get_missing_with_hint()` to verify behavior The hint appears consistently across all scenarios where `uv version` fails to find a project: - `uv version` (normal mode) - `uv version --project .` (explicit project mode) - `uv version --preview` (preview mode) The change maintains all existing functionality - when a `pyproject.toml` is found, `uv version` continues to work normally without showing the hint. Fixes #14730. --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com> --- crates/uv/src/commands/project/version.rs | 33 +++++++++++++++++++---- crates/uv/src/lib.rs | 3 +++ crates/uv/tests/it/version.rs | 10 +++---- 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/crates/uv/src/commands/project/version.rs b/crates/uv/src/commands/project/version.rs index efba226b9..c4b32485d 100644 --- a/crates/uv/src/commands/project/version.rs +++ b/crates/uv/src/commands/project/version.rs @@ -21,7 +21,7 @@ use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_settings::PythonInstallMirrors; use uv_workspace::pyproject_mut::Error; use uv_workspace::{ - DiscoveryOptions, WorkspaceCache, + DiscoveryOptions, WorkspaceCache, WorkspaceError, pyproject_mut::{DependencyTarget, PyProjectTomlMut}, }; use uv_workspace::{VirtualProject, Workspace}; @@ -59,6 +59,7 @@ pub(crate) async fn project_version( output_format: VersionFormat, project_dir: &Path, package: Option, + explicit_project: bool, dry_run: bool, locked: bool, frozen: bool, @@ -78,7 +79,7 @@ pub(crate) async fn project_version( preview: PreviewMode, ) -> Result { // Read the metadata - let project = find_target(project_dir, package.as_ref()).await?; + let project = find_target(project_dir, package.as_ref(), explicit_project).await?; let pyproject_path = project.root().join("pyproject.toml"); let Some(name) = project.project_name().cloned() else { @@ -325,10 +326,30 @@ pub(crate) async fn project_version( Ok(status) } +/// Add hint to use `uv self version` when workspace discovery fails due to missing pyproject.toml +/// and --project was not explicitly passed +fn hint_uv_self_version(err: WorkspaceError, explicit_project: bool) -> anyhow::Error { + if matches!(err, WorkspaceError::MissingPyprojectToml) && !explicit_project { + anyhow!( + "{}\n\n{}{} If you meant to view uv's version, use `{}` instead", + err, + "hint".bold().cyan(), + ":".bold(), + "uv self version".green() + ) + } else { + err.into() + } +} + /// Find the pyproject.toml we're modifying /// /// Note that `uv version` never needs to support PEP 723 scripts, as those are unversioned. -async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Result { +async fn find_target( + project_dir: &Path, + package: Option<&PackageName>, + explicit_project: bool, +) -> Result { // Find the project in the workspace. // No workspace caching since `uv version` changes the workspace definition. let project = if let Some(package) = package { @@ -338,7 +359,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul &DiscoveryOptions::default(), &WorkspaceCache::default(), ) - .await? + .await + .map_err(|err| hint_uv_self_version(err, explicit_project))? .with_current_project(package.clone()) .with_context(|| format!("Package `{package}` not found in workspace"))?, ) @@ -348,7 +370,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul &DiscoveryOptions::default(), &WorkspaceCache::default(), ) - .await? + .await + .map_err(|err| hint_uv_self_version(err, explicit_project))? }; Ok(project) } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 9a67bb877..4a937b0db 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1058,6 +1058,7 @@ async fn run(mut cli: Cli) -> Result { script, globals, cli.top_level.no_config, + cli.top_level.global_args.project.is_some(), filesystem, cache, printer, @@ -1659,6 +1660,7 @@ async fn run_project( globals: GlobalSettings, // TODO(zanieb): Determine a better story for passing `no_config` in here no_config: bool, + explicit_project: bool, filesystem: Option, cache: Cache, printer: Printer, @@ -2050,6 +2052,7 @@ async fn run_project( args.output_format, project_dir, args.package, + explicit_project, args.dry_run, args.locked, args.frozen, diff --git a/crates/uv/tests/it/version.rs b/crates/uv/tests/it/version.rs index 53cb0de06..e2f9f1201 100644 --- a/crates/uv/tests/it/version.rs +++ b/crates/uv/tests/it/version.rs @@ -1607,20 +1607,20 @@ fn version_get_fallback_missing_strict() -> Result<()> { Ok(()) } -// Should error if this pyproject.toml is missing -// and --preview was passed explicitly. +/// Should error with hint if pyproject.toml is missing in normal mode #[test] -fn version_get_fallback_missing_strict_preview() -> Result<()> { +fn version_get_missing_with_hint() -> Result<()> { let context = TestContext::new("3.12"); - uv_snapshot!(context.filters(), context.version() - .arg("--preview"), @r" + uv_snapshot!(context.filters(), context.version(), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: No `pyproject.toml` found in current directory or any parent directory + + hint: If you meant to view uv's version, use `uv self version` instead "); Ok(())