From 2d7070544fe0fe73225fd4d7f9f2ca1dc1b50647 Mon Sep 17 00:00:00 2001 From: Olivier Lacroix Date: Fri, 5 Apr 2024 00:41:54 +1100 Subject: [PATCH] feat: Get tool.pixi.project.name from project.name (#1112) Co-authored-by: Tim de Jager Co-authored-by: Ruben Arts --- docs/advanced/pyproject_toml.md | 5 -- .../pyproject.toml | 1 - src/cli/init.rs | 1 - src/project/manifest/error.rs | 46 ++++++++++++++++++- src/project/manifest/metadata.rs | 2 +- src/project/manifest/mod.rs | 39 ++++++++-------- src/project/manifest/pyproject.rs | 22 ++++++--- src/project/mod.rs | 7 ++- 8 files changed, 89 insertions(+), 34 deletions(-) diff --git a/docs/advanced/pyproject_toml.md b/docs/advanced/pyproject_toml.md index 7018a391e..266e55505 100644 --- a/docs/advanced/pyproject_toml.md +++ b/docs/advanced/pyproject_toml.md @@ -8,7 +8,6 @@ We don't advise to use the `pyproject.toml` file for anything else than python p When you already have a `pyproject.toml` file in your project, you can add the following section to it: ```toml [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] ``` @@ -31,7 +30,6 @@ name = "my_project" requires-python = ">=3.9" [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] ``` @@ -62,7 +60,6 @@ dependencies = [ ] [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] ``` @@ -95,7 +92,6 @@ dependencies = [ ] [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] @@ -121,7 +117,6 @@ dependencies = [ ] [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] diff --git a/examples/flask-hello-world-pyproject/pyproject.toml b/examples/flask-hello-world-pyproject/pyproject.toml index f98149cd1..7fecef713 100644 --- a/examples/flask-hello-world-pyproject/pyproject.toml +++ b/examples/flask-hello-world-pyproject/pyproject.toml @@ -11,7 +11,6 @@ requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.pixi.project] -name = "flask-hello-world-pyproject" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] diff --git a/src/cli/init.rs b/src/cli/init.rs index 756827ea6..d7bbc24dd 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -55,7 +55,6 @@ platforms = ["{{ platforms|join("\", \"") }}"] /// The pyproject.toml template const PYROJECT_TEMPLATE: &str = r#" [tool.pixi.project] -name = "{{ name }}" channels = [{%- if channels %}"{{ channels|join("\", \"") }}"{%- endif %}] platforms = ["{{ platforms|join("\", \"") }}"] diff --git a/src/project/manifest/error.rs b/src/project/manifest/error.rs index 02f391871..d1005c6d0 100644 --- a/src/project/manifest/error.rs +++ b/src/project/manifest/error.rs @@ -1,6 +1,6 @@ use crate::project::manifest::{FeatureName, TargetSelector}; use crate::project::SpecType; -use miette::Diagnostic; +use miette::{Diagnostic, IntoDiagnostic, LabeledSpan, NamedSource, Report}; use rattler_conda_types::{InvalidPackageNameError, ParseMatchSpecError}; use thiserror::Error; @@ -71,3 +71,47 @@ pub enum RequirementConversionError { #[error("Error converting requirement from pypi to conda")] Unimplemented, } + +#[derive(Error, Debug, Clone)] +pub enum TomlError { + #[error("{0}")] + Error(#[from] toml_edit::TomlError), + #[error("Missing field `project`")] + NoProjectTable, + #[error("Missing field `name`")] + NoProjectName(Option>), +} + +impl TomlError { + pub fn to_fancy(&self, file_name: &str, contents: impl Into) -> Result { + if let Some(span) = self.span() { + Err(miette::miette!( + labels = vec![LabeledSpan::at(span, self.message())], + "failed to parse project manifest" + ) + .with_source_code(NamedSource::new(file_name, contents.into()))) + } else { + Err(self.clone()).into_diagnostic() + } + } + + fn span(&self) -> Option> { + match self { + TomlError::Error(e) => e.span(), + TomlError::NoProjectTable => Some(0..1), + TomlError::NoProjectName(span) => span.clone(), + } + } + fn message(&self) -> &str { + match self { + TomlError::Error(e) => e.message(), + TomlError::NoProjectTable => "Missing field `project`", + TomlError::NoProjectName(_) => "Missing field `name`", + } + } +} +impl From for TomlError { + fn from(e: toml_edit::de::Error) -> Self { + TomlError::Error(e.into()) + } +} diff --git a/src/project/manifest/metadata.rs b/src/project/manifest/metadata.rs index f6fc55b43..4d2f78fd9 100644 --- a/src/project/manifest/metadata.rs +++ b/src/project/manifest/metadata.rs @@ -11,7 +11,7 @@ use url::Url; #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub struct ProjectMetadata { /// The name of the project - pub name: String, + pub name: Option, // set as optional to handle conversion from pyproject.toml /// The version of the project #[serde_as(as = "Option")] diff --git a/src/project/manifest/mod.rs b/src/project/manifest/mod.rs index b396735cd..104177745 100644 --- a/src/project/manifest/mod.rs +++ b/src/project/manifest/mod.rs @@ -24,7 +24,7 @@ use indexmap::map::Entry; use indexmap::{Equivalent, IndexMap, IndexSet}; use itertools::Itertools; pub use metadata::ProjectMetadata; -use miette::{miette, Diagnostic, IntoDiagnostic, LabeledSpan, NamedSource, WrapErr}; +use miette::{miette, Diagnostic, IntoDiagnostic, NamedSource, WrapErr}; use pyproject::PyProjectManifest; pub use python::PyPiRequirement; use rattler_conda_types::{ @@ -47,7 +47,9 @@ use std::{ pub use system_requirements::{LibCSystemRequirement, SystemRequirements}; pub use target::{Target, TargetSelector, Targets}; use thiserror::Error; -use toml_edit::{DocumentMut, TomlError}; +use toml_edit::DocumentMut; + +use self::error::TomlError; /// Errors that can occur when getting a feature. #[derive(Debug, Clone, Error, Diagnostic)] @@ -127,21 +129,14 @@ impl Manifest { ), }; - let (manifest, document) = match parsed - .and_then(|manifest| contents.parse::().map(|doc| (manifest, doc))) - { + let (manifest, document) = match parsed.and_then(|manifest| { + contents + .parse::() + .map(|doc| (manifest, doc)) + .map_err(TomlError::from) + }) { Ok(result) => result, - Err(e) => { - if let Some(span) = e.span() { - return Err(miette::miette!( - labels = vec![LabeledSpan::at(span, e.message())], - "failed to parse project manifest" - ) - .with_source_code(NamedSource::new(file_name, contents))); - } else { - return Err(e).into_diagnostic(); - } - } + Err(e) => e.to_fancy(file_name, &contents)?, }; // Validate the contents of the manifest @@ -777,7 +772,15 @@ pub struct ProjectManifest { impl ProjectManifest { /// Parses a toml string into a project manifest. pub fn from_toml_str(source: &str) -> Result { - toml_edit::de::from_str(source).map_err(TomlError::from) + let manifest: ProjectManifest = toml_edit::de::from_str(source).map_err(TomlError::from)?; + + // Make sure project.name is defined + if manifest.project.name.is_none() { + let span = source.parse::().map_err(TomlError::from)?["project"].span(); + return Err(TomlError::NoProjectName(span)); + } + + Ok(manifest) } /// Returns the default feature. @@ -1118,7 +1121,7 @@ mod tests { // From PathBuf let manifest = Manifest::from_path(path).unwrap(); - assert_eq!(manifest.parsed.project.name, "foo"); + assert_eq!(manifest.parsed.project.name.unwrap(), "foo"); assert_eq!( manifest.parsed.project.version, Some(Version::from_str("0.1.0").unwrap()) diff --git a/src/project/manifest/pyproject.rs b/src/project/manifest/pyproject.rs index 65bcd5d85..eb9a554db 100644 --- a/src/project/manifest/pyproject.rs +++ b/src/project/manifest/pyproject.rs @@ -3,11 +3,11 @@ use rattler_conda_types::{NamelessMatchSpec, PackageName, ParseStrictness::Lenie use serde::Deserialize; use std::str::FromStr; use toml_edit; -use toml_edit::TomlError; use super::{ - error::RequirementConversionError, python::PyPiPackageName, ProjectManifest, PyPiRequirement, - SpecType, + error::{RequirementConversionError, TomlError}, + python::PyPiPackageName, + ProjectManifest, PyPiRequirement, SpecType, }; #[derive(Deserialize, Debug, Clone)] @@ -33,7 +33,17 @@ impl std::ops::Deref for PyProjectManifest { impl PyProjectManifest { /// Parses a toml string into a pyproject manifest. pub fn from_toml_str(source: &str) -> Result { - toml_edit::de::from_str(source).map_err(TomlError::from) + let manifest: PyProjectManifest = + toml_edit::de::from_str(source).map_err(TomlError::from)?; + + // Make sure [project] exists in pyproject.toml, + // This will ensure project.name is defined + // TODO: do we want to Err if tool.pixi.name is defined? + if manifest.project.is_none() { + return Err(TomlError::NoProjectTable); + } + + Ok(manifest) } } @@ -47,8 +57,9 @@ impl From for ProjectManifest { .as_ref() .expect("the [project] table should exist"); - // TODO: tool.pixi.project.name should be made optional or read from project.name + // Get tool.pixi.project.name from project.name // TODO: could copy across / convert some other optional fields if relevant + manifest.project.name = item.project.as_ref().map(|p| p.name.clone()); // Add python as dependency based on the project.requires_python property (if any) let pythonspec = pyproject @@ -118,7 +129,6 @@ mod tests { name = "project" [tool.pixi.project] - name = "project" version = "0.1.0" description = "A project" authors = ["Author "] diff --git a/src/project/mod.rs b/src/project/mod.rs index ae454a417..34a2e0233 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -220,7 +220,12 @@ impl Project { /// Returns the name of the project pub fn name(&self) -> &str { - &self.manifest.parsed.project.name + self.manifest + .parsed + .project + .name + .as_ref() + .expect("name should always be defined.") } /// Returns the version of the project