Surface dedicated `project.name` error for workspaces (#7399)

## Summary

An extension of https://github.com/astral-sh/uv/pull/6803 to cover `uv
run`.
This commit is contained in:
Charlie Marsh 2024-09-14 16:46:21 -04:00 committed by GitHub
parent 4aad89cf06
commit e07281deb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 37 additions and 16 deletions

View File

@ -47,7 +47,7 @@ pub enum MetadataError {
MailParse(#[from] MailParseError), MailParse(#[from] MailParseError),
#[error("Invalid `pyproject.toml`")] #[error("Invalid `pyproject.toml`")]
InvalidPyprojectTomlSyntax(#[source] toml_edit::TomlError), 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), InvalidPyprojectTomlMissingName(#[source] toml_edit::de::Error),
#[error(transparent)] #[error(transparent)]
InvalidPyprojectTomlSchema(toml_edit::de::Error), InvalidPyprojectTomlSchema(toml_edit::de::Error),

View File

@ -6,12 +6,12 @@
//! //!
//! Then lowers them into a dependency specification. //! Then lowers them into a dependency specification.
use glob::Pattern;
use serde::{de::IntoDeserializer, Deserialize, Serialize};
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{collections::BTreeMap, mem}; use std::{collections::BTreeMap, mem};
use glob::Pattern;
use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use url::Url; use url::Url;
@ -22,6 +22,16 @@ use uv_git::GitReference;
use uv_macros::OptionsMetadata; use uv_macros::OptionsMetadata;
use uv_normalize::{ExtraName, PackageName}; 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. /// A `pyproject.toml` as specified in PEP 517.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
@ -41,8 +51,18 @@ pub struct PyProjectToml {
impl PyProjectToml { impl PyProjectToml {
/// Parse a `PyProjectToml` from a raw TOML string. /// Parse a `PyProjectToml` from a raw TOML string.
pub fn from_string(raw: String) -> Result<Self, toml::de::Error> { pub fn from_string(raw: String) -> Result<Self, PyprojectTomlError> {
let pyproject = toml::from_str(&raw)?; 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 }) Ok(PyProjectToml { raw, ..pyproject })
} }

View File

@ -1,11 +1,10 @@
//! Resolve the current [`ProjectWorkspace`] or [`Workspace`]. //! Resolve the current [`ProjectWorkspace`] or [`Workspace`].
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use either::Either; use either::Either;
use glob::{glob, GlobError, PatternError}; use glob::{glob, GlobError, PatternError};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use tracing::{debug, trace, warn}; use tracing::{debug, trace, warn};
use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl}; use pep508_rs::{MarkerTree, RequirementOrigin, VerbatimUrl};
@ -14,7 +13,9 @@ use uv_fs::{Simplified, CWD};
use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES}; use uv_normalize::{GroupName, PackageName, DEV_DEPENDENCIES};
use uv_warnings::{warn_user, warn_user_once}; 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)] #[derive(thiserror::Error, Debug)]
pub enum WorkspaceError { pub enum WorkspaceError {
@ -39,7 +40,7 @@ pub enum WorkspaceError {
#[error(transparent)] #[error(transparent)]
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
#[error("Failed to parse: `{}`", _0.user_display())] #[error("Failed to parse: `{}`", _0.user_display())]
Toml(PathBuf, #[source] Box<toml::de::Error>), Toml(PathBuf, #[source] Box<PyprojectTomlError>),
#[error("Failed to normalize workspace member path")] #[error("Failed to normalize workspace member path")]
Normalize(#[source] std::io::Error), Normalize(#[source] std::io::Error),
} }
@ -120,7 +121,7 @@ impl Workspace {
let pyproject_path = project_path.join("pyproject.toml"); let pyproject_path = project_path.join("pyproject.toml");
let contents = fs_err::tokio::read_to_string(&pyproject_path).await?; 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)))?; .map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?;
// Check if the project is explicitly marked as unmanaged. // Check if the project is explicitly marked as unmanaged.
@ -588,7 +589,7 @@ impl Workspace {
let pyproject_path = workspace_root.join("pyproject.toml"); let pyproject_path = workspace_root.join("pyproject.toml");
let contents = fs_err::read_to_string(&pyproject_path)?; let contents = fs_err::read_to_string(&pyproject_path)?;
let pyproject_toml = PyProjectToml::from_string(contents) 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!( debug!(
"Adding root workspace member: `{}`", "Adding root workspace member: `{}`",
@ -699,7 +700,6 @@ impl Workspace {
} }
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
}; };
let pyproject_toml = PyProjectToml::from_string(contents) let pyproject_toml = PyProjectToml::from_string(contents)
.map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?; .map_err(|err| WorkspaceError::Toml(pyproject_path.clone(), Box::new(err)))?;

View File

@ -12729,7 +12729,7 @@ fn lock_invalid_project_table() -> Result<()> {
Using Python 3.12.[X] interpreter at: [PYTHON-3.12] Using Python 3.12.[X] interpreter at: [PYTHON-3.12]
error: Failed to build: `b @ file://[TEMP_DIR]/b` error: Failed to build: `b @ file://[TEMP_DIR]/b`
Caused by: Failed to extract static metadata from `pyproject.toml` 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 Caused by: TOML parse error at line 2, column 10
| |
2 | [project.urls] 2 | [project.urls]

View File

@ -1912,7 +1912,7 @@ fn run_exit_code() -> Result<()> {
} }
#[test] #[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 context = TestContext::new_with_versions(&["3.12", "3.11", "3.8"]);
let pyproject_toml = context.temp_dir.child("pyproject.toml"); let pyproject_toml = context.temp_dir.child("pyproject.toml");
@ -1939,6 +1939,7 @@ fn run_lock_invalid_project_table() -> Result<()> {
----- stderr ----- ----- stderr -----
error: Failed to parse: `pyproject.toml` 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 Caused by: TOML parse error at line 1, column 2
| |
1 | [project.urls] 1 | [project.urls]