use std::path::{Path, PathBuf}; use anyhow::Context; use semver::Version; use serde::{Deserialize, Serialize}; use tokio::fs; use crate::shared::Architecture; /// A Nye package manifest. /// /// It describes the package's general information, dependencies, exposed artifacts, and other /// things. It's used by the Nye package manager to know how to install, update, and remove the /// package. /// /// This is currently stored at the package's root directory in a file called `Nye.toml`. The file /// is in TOML format, and is deserialized into this struct when the package manager needs to read /// it. #[derive(Clone, Serialize, Deserialize)] pub struct ProjectManifest { /// The package's general information, such as its name, version, and description. pub package: ProjectManifestPackage, /// The package's exposed binaries, libraries, and other artifacts. pub exposed: ProjectManifestExposed, } impl ProjectManifest { /// Validates the manifest, ensuring that the values are well-formed and consistent. /// /// Returns: /// * `Ok(())` - When the manifest is valid. /// * `Err(anyhow::Error)` - When the manifest is invalid, with a message describing the issue. pub fn validate(&self) -> anyhow::Result<()> { if self.package.architectures.is_empty() { anyhow::bail!("The package must support at least one architecture."); } for (i, architecture) in self.package.architectures.iter().enumerate() { if self .package .architectures .iter() .position(|a| a == architecture) != Some(i) { anyhow::bail!( "The package's architectures must be unique, yet {:?} appears more than once.", architecture ); } } Ok(()) } } /// A Nye package's general information, such as its name, version, and description. #[derive(Clone, Serialize, Deserialize)] pub struct ProjectManifestPackage { /// The package's name, which is used to identify it in the Nye package manager. pub name: String, /// The package's version, which is used to identify it in the Nye package manager. pub version: Version, /// The package's description, which is used to describe it in the Nye package manager. pub description: Option, /// The package's supported architectures, which is used to determine if the package can be /// installed on the current system. pub architectures: Vec, } /// A Nye package's exposed binaries, libraries, and other artifacts. /// /// Only the artifacts listed here are exposed to the user and dependants. #[derive(Default, Clone, Serialize, Deserialize)] pub struct ProjectManifestExposed { /// The package's exposed binaries, which are the executables that the package provides to the /// user. #[serde(rename = "bin")] pub binaries: Vec, } /// A Nye package's exposed binary, which is an executable that the package provides to the user. #[derive(Clone, Serialize, Deserialize)] pub struct ProjectManifestExposeBinary { /// The name under which the binary is exposed. pub name: String, /// The path to the binary, relative to the `./src/{arch}/bin` directory (which is itself /// relative to the `Nye.toml` file). /// /// If missing, it is assumed that the binary is located at `./src/{arch}/bin/{name}`. pub path: Option, /// The architectures for which the binary is available. /// /// If not specified, it is assumed that the binary is available for all architectures /// specified in the `package.architectures` field. pub architectures: Option>, } /// Reads a Nye package's manifest from the given path and returns it as a [`Manifest`] struct. /// /// Arguments: /// * `path` - The path to the manifest file. This is usually the `Nye.toml` file at the root of /// the package's directory. /// /// Returns: /// * `Ok(Manifest)` - The manifest struct if the file was read and parsed successfully. /// * `Err(anyhow::Error)` - An error if the file could not be read or parsed. pub async fn read(path: impl AsRef) -> anyhow::Result { let string = fs::read_to_string(path) .await .context("Could not read package's manifest.")?; parse(&string) } /// Parses a Nye package's manifest from the given string and returns it as a [`Manifest`] struct. /// /// Arguments: /// * `string` - The string containing the manifest in TOML format. /// /// Returns: /// * `Ok(Manifest)` - The manifest struct if the string was parsed successfully. /// * `Err(anyhow::Error)` - An error if the string could not be parsed. pub fn parse(string: &str) -> anyhow::Result { let manifest: ProjectManifest = toml::from_str(string).context("Could not parse the package's manifest.")?; Ok(manifest) }