diff --git a/docs/advanced/pyproject_toml.md b/docs/advanced/pyproject_toml.md index c55f362e0..daa345ab1 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"] ``` @@ -28,7 +27,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"] ``` @@ -59,7 +57,6 @@ dependencies = [ ] [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] ``` @@ -92,7 +89,6 @@ dependencies = [ ] [tool.pixi.project] -name = "my_project" channels = ["conda-forge"] platforms = ["linux-64", "osx-arm64", "osx-64", "win-64"] @@ -118,7 +114,6 @@ dependencies = [ ] [tool.pixi.project] -name = "my_project" 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 88089297a..bf7f99bfa 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -54,7 +54,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..5f9131ee2 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,48 @@ pub enum RequirementConversionError { #[error("Error converting requirement from pypi to conda")] Unimplemented, } + +#[derive(Error, Debug, Clone)] +pub enum TomlError { + #[error("failed to parse project manifest")] + Error(#[from] toml_edit::TomlError), + #[error("'pyproject.toml' should contain a [project] table")] + NoProjectTable(std::ops::Range), + #[error("The [project] table should contain a 'name'")] + NoProjectName(Option>), +} + +impl TomlError { + pub fn to_fancy(self, file_name: &str, contents: impl Into) -> Result { + if let Some(span) = self.clone().span() { + return 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 { + return Err(self).into_diagnostic(); + } + } + + fn span(self) -> Option> { + match self { + TomlError::Error(e) => e.span(), + TomlError::NoProjectTable(span) => Some(span), + TomlError::NoProjectName(span) => span, + } + } + 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 { + toml_edit::TomlError::from(e).into() + } +} diff --git a/src/project/manifest/metadata.rs b/src/project/manifest/metadata.rs index f6fc55b43..18010279e 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, /// 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..b08e8dbf3 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)?; + + // project is not optional in pixi.toml, but name is + 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 a6bedc9d6..073aa4d3c 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,16 @@ 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)?; + + // project is optional in pyproject.toml, but name is not + // TODO: do we want to Err if tool.pixi.name is defined? + if manifest.project.is_none() { + return Err(TomlError::NoProjectTable(0..1)); + } + + Ok(manifest) } } @@ -42,8 +51,9 @@ impl From for ProjectManifest { // Start by loading the data nested under "tool.pixi" let mut manifest = item.tool.pixi.clone(); - // 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 = item diff --git a/src/project/mod.rs b/src/project/mod.rs index ae454a417..17ec28ecd 100644 --- a/src/project/mod.rs +++ b/src/project/mod.rs @@ -220,7 +220,13 @@ 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