From e07281deb3478b0ac038016f42dcdbc29fa3a2dc Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 14 Sep 2024 16:46:21 -0400 Subject: [PATCH] Surface dedicated `project.name` error for workspaces (#7399) ## Summary An extension of https://github.com/astral-sh/uv/pull/6803 to cover `uv run`. --- crates/pypi-types/src/metadata.rs | 2 +- crates/uv-workspace/src/pyproject.rs | 30 +++++++++++++++++++++++----- crates/uv-workspace/src/workspace.rs | 16 +++++++-------- crates/uv/tests/lock.rs | 2 +- crates/uv/tests/run.rs | 3 ++- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/crates/pypi-types/src/metadata.rs b/crates/pypi-types/src/metadata.rs index c162a3d4c97a..f81b4fe1dee6 100644 --- a/crates/pypi-types/src/metadata.rs +++ b/crates/pypi-types/src/metadata.rs @@ -47,7 +47,7 @@ pub enum MetadataError { MailParse(#[from] MailParseError), #[error("Invalid `pyproject.toml`")] InvalidPyprojectTomlSyntax(#[source] toml_edit::TomlError), - #[error("`pyproject.toml` is using the `[project]` table, but the required `project.name` is not set.")] + #[error("`pyproject.toml` is using the `[project]` table, but the required `project.name` field is not set.")] InvalidPyprojectTomlMissingName(#[source] toml_edit::de::Error), #[error(transparent)] InvalidPyprojectTomlSchema(toml_edit::de::Error), diff --git a/crates/uv-workspace/src/pyproject.rs b/crates/uv-workspace/src/pyproject.rs index a40332ed6abc..dc0609caa63b 100644 --- a/crates/uv-workspace/src/pyproject.rs +++ b/crates/uv-workspace/src/pyproject.rs @@ -6,12 +6,12 @@ //! //! Then lowers them into a dependency specification. +use glob::Pattern; +use serde::{de::IntoDeserializer, Deserialize, Serialize}; use std::ops::Deref; use std::path::{Path, PathBuf}; +use std::str::FromStr; use std::{collections::BTreeMap, mem}; - -use glob::Pattern; -use serde::{Deserialize, Serialize}; use thiserror::Error; use url::Url; @@ -22,6 +22,16 @@ use uv_git::GitReference; use uv_macros::OptionsMetadata; use uv_normalize::{ExtraName, PackageName}; +#[derive(Error, Debug)] +pub enum PyprojectTomlError { + #[error(transparent)] + TomlSyntax(#[from] toml_edit::TomlError), + #[error(transparent)] + TomlSchema(#[from] toml_edit::de::Error), + #[error("`pyproject.toml` is using the `[project]` table, but the required `project.name` field is not set")] + MissingName(#[source] toml_edit::de::Error), +} + /// A `pyproject.toml` as specified in PEP 517. #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "kebab-case")] @@ -41,8 +51,18 @@ pub struct PyProjectToml { impl PyProjectToml { /// Parse a `PyProjectToml` from a raw TOML string. - pub fn from_string(raw: String) -> Result { - let pyproject = toml::from_str(&raw)?; + pub fn from_string(raw: String) -> Result { + let pyproject: toml_edit::ImDocument<_> = + toml_edit::ImDocument::from_str(&raw).map_err(PyprojectTomlError::TomlSyntax)?; + let pyproject = + PyProjectToml::deserialize(pyproject.into_deserializer()).map_err(|err| { + // TODO(konsti): A typed error would be nicer, this can break on toml upgrades. + if err.message().contains("missing field `name`") { + PyprojectTomlError::MissingName(err) + } else { + PyprojectTomlError::TomlSchema(err) + } + })?; Ok(PyProjectToml { raw, ..pyproject }) } diff --git a/crates/uv-workspace/src/workspace.rs b/crates/uv-workspace/src/workspace.rs index 10548c964982..62a802e635b2 100644 --- a/crates/uv-workspace/src/workspace.rs +++ b/crates/uv-workspace/src/workspace.rs @@ -1,11 +1,10 @@ //! Resolve the current [`ProjectWorkspace`] or [`Workspace`]. -use std::collections::BTreeMap; -use std::path::{Path, PathBuf}; - use either::Either; use glob::{glob, GlobError, PatternError}; use rustc_hash::FxHashSet; +use std::collections::BTreeMap; +use std::path::{Path, PathBuf}; use tracing::{debug, trace, warn}; use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl}; @@ -14,7 +13,9 @@ use uv_fs::{Simplified, CWD}; use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES}; use uv_warnings::{warn_user, warn_user_once}; -use crate::pyproject::{Project, PyProjectToml, Source, ToolUvSources, ToolUvWorkspace}; +use crate::pyproject::{ + Project, PyProjectToml, PyprojectTomlError, Source, ToolUvSources, ToolUvWorkspace, +}; #[derive(thiserror::Error, Debug)] pub enum WorkspaceError { @@ -39,7 +40,7 @@ pub enum WorkspaceError { #[error(transparent)] Io(#[from] std::io::Error), #[error("Failed to parse: `{}`", _0.user_display())] - Toml(PathBuf, #[source] Box), + Toml(PathBuf, #[source] Box), #[error("Failed to normalize workspace member path")] Normalize(#[source] std::io::Error), } @@ -120,7 +121,7 @@ impl Workspace { let pyproject_path = project_path.join("pyproject.toml"); let contents = fs_err::tokio::read_to_string(&pyproject_path).await?; - let pyproject_toml = PyProjectToml::from_string(contents.clone()) + let pyproject_toml = PyProjectToml::from_string(contents) .map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?; // Check if the project is explicitly marked as unmanaged. @@ -588,7 +589,7 @@ impl Workspace { let pyproject_path = workspace_root.join("pyproject.toml"); let contents = fs_err::read_to_string(&pyproject_path)?; let pyproject_toml = PyProjectToml::from_string(contents) - .map_err(|err| WorkspaceError::Toml(pyproject_path, Box::new(err)))?; + .map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?; debug!( "Adding root workspace member: `{}`", @@ -699,7 +700,6 @@ impl Workspace { } Err(err) => return Err(err.into()), }; - let pyproject_toml = PyProjectToml::from_string(contents) .map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?; diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index 69b20c7d3a5b..5a70353466ea 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -12729,7 +12729,7 @@ fn lock_invalid_project_table() -> Result<()> { Using Python 3.12.[X] interpreter at: [PYTHON-3.12] error: Failed to build: `b @ file://[TEMP_DIR]/b` Caused by: Failed to extract static metadata from `pyproject.toml` - Caused by: `pyproject.toml` is using the `[project]` table, but the required `project.name` is not set. + Caused by: `pyproject.toml` is using the `[project]` table, but the required `project.name` field is not set. Caused by: TOML parse error at line 2, column 10 | 2 | [project.urls] diff --git a/crates/uv/tests/run.rs b/crates/uv/tests/run.rs index 064f8377bf27..c88749b4b539 100644 --- a/crates/uv/tests/run.rs +++ b/crates/uv/tests/run.rs @@ -1912,7 +1912,7 @@ fn run_exit_code() -> Result<()> { } #[test] -fn run_lock_invalid_project_table() -> Result<()> { +fn run_invalid_project_table() -> Result<()> { let context = TestContext::new_with_versions(&["3.12", "3.11", "3.8"]); let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -1939,6 +1939,7 @@ fn run_lock_invalid_project_table() -> Result<()> { ----- stderr ----- error: Failed to parse: `pyproject.toml` + Caused by: `pyproject.toml` is using the `[project]` table, but the required `project.name` field is not set Caused by: TOML parse error at line 1, column 2 | 1 | [project.urls]